【问题标题】:Mocha / Chai expect.to.throw not catching thrown errorsMocha / Chai expect.to.throw 没有捕捉到抛出的错误
【发布时间】:2014-03-02 11:12:10
【问题描述】:

我无法让 Chai 的 expect.to.throw 在我的 node.js 应用程序的测试中工作。测试在抛出的错误上一直失败,但是如果我将测试用例包装在 try 中并捕获并断言捕获的错误,它就可以工作。

expect.to.throw 是否不像我认为的那样工作?

it('should throw an error if you try to get an undefined property', function (done) {
  var params = { a: 'test', b: 'test', c: 'test' };
  var model = new TestModel(MOCK_REQUEST, params);

  // neither of these work
  expect(model.get('z')).to.throw('Property does not exist in model schema.');
  expect(model.get('z')).to.throw(new Error('Property does not exist in model schema.'));

  // this works
  try { 
    model.get('z'); 
  }
  catch(err) {
    expect(err).to.eql(new Error('Property does not exist in model schema.'));
  }

  done();
});

失败:

19 passing (25ms)
  1 failing

  1) Model Base should throw an error if you try to get an undefined property:
     Error: Property does not exist in model schema.

【问题讨论】:

    标签: javascript node.js mocha.js chai


    【解决方案1】:

    您必须将函数传递给expect。像这样:

    expect(model.get.bind(model, 'z')).to.throw('Property does not exist in model schema.');
    expect(model.get.bind(model, 'z')).to.throw(new Error('Property does not exist in model schema.'));
    

    按照您的操作方式,您将调用model.get('z')结果传递给expect。但是要测试是否有东西被抛出,你必须将一个函数传递给expectexpect 会调用它自己。上面使用的bind 方法创建了一个新函数,调用该函数时将调用model.get,其中this 设置为model 的值,第一个参数设置为'z'

    bind 的一个很好的解释可以找到here

    【讨论】:

    • 我确实通过了一个函数,不是吗? model 实例有一个名为 get 的函数,我在期望中传递/调用了它。
    • 不,请参阅我在您撰写评论时添加的说明。
    • 哎呀。为什么文档 (chaijs.com/api/bdd/#throw) 不演示这种绑定用法?似乎to.throw 最常见的测试场景是测试函数中的特定条件,这需要使用无效状态/参数调用该函数。 (就此而言....为什么 chaijs.com 的深度链接实际上不进行深度链接?)
    • 请注意(截至 2017 年 9 月)这不适用于异步函数:请参阅 github.com/chaijs/chai/issues/882#issuecomment-322131680 和相关讨论。
    • 感谢@ChrisV 的评论!通过阅读您的评论并转到链接,我能够解决我的问题!
    【解决方案2】:

    作为this answer says,您也可以像这样将代码包装在匿名函数中:

    expect(function(){
        model.get('z');
    }).to.throw('Property does not exist in model schema.');
    

    【讨论】:

    • 这不适用于异步函数调用。假设 model.get 是返回 promise 的异步。但是它会引发错误。如果我尝试上述方法,那就是“超时”,因为我们必须向 mocha 通知“完成”。同时我也不能尝试expect(function(){ model.get('z'); }).to.throw('Property does not exist in model schema.').notify(done);因为没有notify方法。
    • @AnandN 如果我理解你的问题,这听起来你只需要重构你的代码来处理错误。异步函数中未处理的错误不会在您的实际应用中也成为问题吗?
    • 感谢 twiz 的回复。我们在一个集成环境中工作,using 模块负责捕获异常。所以,问题是当我们尝试运行单元测试用例时。最后我们使用下面的方法让它工作catch (err) { expect(err).equal('Error message to be checked'); done(); }
    • 很好的解决方案,除非您在要调用的函数中使用this。那么.bind 是正确的选择。
    • @AnandN 异步函数调用不抛出,它拒绝。为了将来参考,chai-as-promised 处理得很好。
    【解决方案3】:

    如果你已经在使用 ES6/ES2015,那么你也可以使用箭头函数。与使用普通匿名函数基本相同,但更短。

    expect(() => model.get('z')).to.throw('Property does not exist in model schema.');
    

    【讨论】:

    • 这可能有问题,因为箭头函数将其周围的范围用于this
    • @Relic 是的,非常正确。这也是箭头函数的一大优势。箭头函数从它们创建的范围“继承”this。这通常是一个优势,因为它避免了手动将 binding 函数添加到它们的 this 对象。
    • @StijndeWitt 这不是优点或缺点,而是范围控制和有意的。它实际上是使用bind 的语法糖,并且总是绑定到父作用域的this。我在评论中的意图只是为了确保读者意识到潜在的陷阱。
    • @Relic 是的,我同意你的看法。它可以发挥优势,也可以成为使用箭头函数的好理由。
    【解决方案4】:

    这个问题有很多很多重复项,包括未提及 Chai 断言库的问题。以下是汇总的基础知识:

    断言必须调用函数,而不是立即计算。

    assert.throws(x.y.z);      
       // FAIL.  x.y.z throws an exception, which immediately exits the
       // enclosing block, so assert.throw() not called.
    assert.throws(()=>x.y.z);  
       // assert.throw() is called with a function, which only throws
       // when assert.throw executes the function.
    assert.throws(function () { x.y.z });   
       // if you cannot use ES6 at work
    function badReference() { x.y.z }; assert.throws(badReference);  
       // for the verbose
    assert.throws(()=>model.get(z));  
       // the specific example given.
    homegrownAssertThrows(model.get, z);
       //  a style common in Python, but not in JavaScript
    

    您可以使用任何断言库检查特定错误:

    Node

      assert.throws(() => x.y.z);
      assert.throws(() => x.y.z, ReferenceError);
      assert.throws(() => x.y.z, ReferenceError, /is not defined/);
      assert.throws(() => x.y.z, /is not defined/);
      assert.doesNotThrow(() => 42);
      assert.throws(() => x.y.z, Error);
      assert.throws(() => model.get.z, /Property does not exist in model schema./)
    

    Should

      should.throws(() => x.y.z);
      should.throws(() => x.y.z, ReferenceError);
      should.throws(() => x.y.z, ReferenceError, /is not defined/);
      should.throws(() => x.y.z, /is not defined/);
      should.doesNotThrow(() => 42);
      should.throws(() => x.y.z, Error);
      should.throws(() => model.get.z, /Property does not exist in model schema./)
    

    Chai Expect

      expect(() => x.y.z).to.throw();
      expect(() => x.y.z).to.throw(ReferenceError);
      expect(() => x.y.z).to.throw(ReferenceError, /is not defined/);
      expect(() => x.y.z).to.throw(/is not defined/);
      expect(() => 42).not.to.throw();
      expect(() => x.y.z).to.throw(Error);
      expect(() => model.get.z).to.throw(/Property does not exist in model schema./);
    

    您必须处理“逃避”测试的异常

    it('should handle escaped errors', function () {
      try {
        expect(() => x.y.z).not.to.throw(RangeError);
      } catch (err) {
        expect(err).to.be.a(ReferenceError);
      }
    });
    

    起初这看起来很混乱。就像骑自行车一样,它只会在咔哒一声后永远“咔哒”一声。

    【讨论】:

      【解决方案5】:

      来自doc...的示例;)

      因为你依赖this 上下文:

      • 当函数被.throw调用时丢失
      • 它无法知道这应该是什么

      您必须使用以下选项之一:

      • 包装在另一个函数中的方法或函数调用
      • 绑定上下文

        // wrap the method or function call inside of another function
        expect(function () { cat.meow(); }).to.throw();  // Function expression
        expect(() => cat.meow()).to.throw();             // ES6 arrow function
        
        // bind the context
        expect(cat.meow.bind(cat)).to.throw();           // Bind
        

      【讨论】:

      • 这也是我的做法。我发现,ES6 实现是迄今为止最易读的一个
      【解决方案6】:

      另一种可能的实现,比 .bind() 解决方案更麻烦,但有助于说明 expect() 需要一个为覆盖函数提供 this 上下文的函数,您可以使用 @ 987654322@,例如,

      expect(function() {model.get.call(model, 'z');}).to.throw('...');

      【讨论】:

        【解决方案7】:

        我找到了一个很好的解决方法:

        // The test, BDD style
        it ("unsupported site", () => {
            The.function(myFunc)
            .with.arguments({url:"https://www.ebay.com/"})
            .should.throw(/unsupported/);
        });
        
        
        // The function that does the magic: (lang:TypeScript)
        export const The = {
            'function': (func:Function) => ({
                'with': ({
                    'arguments': function (...args:any) {
                        return () => func(...args);
                    }
                })
            })
        };
        

        它比我的旧版本更具可读性:

        it ("unsupported site", () => {
            const args = {url:"https://www.ebay.com/"}; //Arrange
            function check_unsupported_site() { myFunc(args) } //Act
            check_unsupported_site.should.throw(/unsupported/) //Assert
        });
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-02-14
          • 1970-01-01
          • 2020-10-17
          • 1970-01-01
          • 2017-11-14
          • 1970-01-01
          • 2012-03-31
          • 1970-01-01
          相关资源
          最近更新 更多