简体   繁体   English

如何使用Mocha + Chai编写测试以期望setTimeout有异常?

[英]How to write a test using Mocha+Chai to expect an exception from setTimeout?

I have following: 我有以下内容:

it('invalid use', () => {
  Matcher(1).case(1, () => {});
});

The case method is supposed to throw after some delay, how can I describe it for Mocha/Chai that's what I want - the test should pass (and must not pass when exception is not thrown)? case方法应该在一些延迟后抛出,我该如何为Mocha / Chai描述它,这就是我想要的-测试应该通过(并且在未引发异常时必须不通过)?

Consider case method off limits, it cannot be changed. 考虑case方法的限制,无法更改。

For testing purposes it should be equivalent to: 出于测试目的,它应等效于:

it('setTimeout throw', _ => {
  setTimeout(() => { throw new Error(); }, 1); // this is given, cannot be modified
});

I tried: 我试过了:

it('invalid use', done => {
  Matcher(1).case(1, () => {});
  // calls done callback after 'case' may throw
  setTimeout(() => done(), MatcherConfig.execCheckTimeout + 10);
});

But that's not really helping me, because the test behavior is exactly reverted - when an exception from case ( setTimeout ) is not thrown, it passes (should fail) and when an exception is thrown the test fails (should succeed). 但这并不能真正帮助我,因为测试行为已完全还原-当未引发case异常( setTimeout )时,它通过(应该失败),而当引发异常时,测试失败(应该成功)。

I read somewhere someone mentioning global error handler, but I would like to solve this cleanly using Mocha and/or Chai, if it is possible (I guess Mocha is already using it in some way). 我在某处读到有人提到全局错误处理程序,但如果可能的话,我想使用Mocha和/或Chai彻底解决此问题(我想Mocha已经在以某种方式使用它了)。

You cannot handle exceptions from within a asynchronous callback, eg see Handle error from setTimeout . 您不能从异步回调中处理异常,例如,请参见从setTimeout处理错误 This has to do with the execution model ECMAScript uses. 这与ECMAScript使用的执行模型有关。 I suppose the only way to catch it is in fact to employ some environment-specific global error handling, eg process.on('uncaughtException', ...) in Node.js. 我想捕获它的唯一方法实际上是采用某些特定于环境的全局错误处理,例如Node.js中的process.on('uncaughtException', ...)

If you convert your function to Promises, however, you can easily test it using the Chai plugin chai-as-promsied : 但是,如果将函数转换为Promises,则可以使用Chai插件chai-as-promsied轻松测试它:

import * as chai from 'chai';

import chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;

it('invalid use', async () => {
  await expect(Matcher(1).case(1, () => {})).to.eventually.be.rejected;
});

Any Mocha statements like before , after or it will work asynchronously if you return a promise. 任何摩卡之类的语句beforeafter还是it会异步如果返回一个承诺工作。 I generally use something like the below for async tests. 我通常将以下内容用于异步测试。 Also don't forget to set timeout this.timeout(...) if you expect the async function to take more than 2 seconds. 如果您希望异步功能花费2秒钟以上的时间,也不要忘记设置超时this.timeout(...)

it('some test', () => {
    return new Promise(function(resolve,reject){
        SomeAsyncFunction(function(error,vals) {
            if(error) {
                 return reject(error);    
            } else {
                try {
                    //do some chai tests here
                } catch(e) {
                    return reject(e);
                }
                return resolve();
            }
        });
    });
});

Specifically for your case, since we expect some error to be thrown after a period of time (assuming the empty callback you have provided to .case should not run due to the exception being thrown) then you can write it something like: 专门针对您的情况,由于我们希望在一段时间后会引发一些错误(假设由于引发了异常,您提供给.case的空回调不应该运行),因此您可以编写以下内容:

it('invalid use', () => {
    //define the promise to run the async function
    let prom = new Promise(function(resolve,reject){
        //reject the promise if the function does not throw an error
        //note I am assuming that the callback won't run if the error is thrown
        //also note this error will be passed to prom.catch so need to do some test to make sure it's not the error you are looking for.
        Matcher(1).case(1, () => {return reject(new Error('did not throw'))});
    });
    prom.catch(function(err){
        try {
            expect(err).to.be.an('error');
            expect(err.message).to.not.equal('did not throw');
            //more checks to see if err is the error you are looking for
        } catch(e) {
            //err was not the error you were looking for
            return Promise.reject(e);
        }
        //tests passed
        return Promise.resolve();
    });
    //since it() receives a promise as a return value it will pass or fail the test based on the promise.
    return prom;
});

If your tested code calls setTimeout with a callback that throws and no-one is catching this is exception then: 如果您测试的代码使用抛出的回调调用setTimeout ,但没有人捕获到此异常,则:

1) this code is broken 1)此代码已损坏

2) the only way to see that problem is platform global exception handler like process.on('uncaughtException' mentioned by user ComFreek 2)看到该问题的唯一方法是平台全局异常处理程序,例如用户ComFreek提到的process.on('uncaughtException'

The last resort chance is to stub setTimeout for duration of test (for example using sinon.stub ) or just manually. 最后的机会是将setTimeout存根以进行测试(例如,使用sinon.stub )或仅手动进行sinon.stub

In such stubbed setTimeout you can decorate timeout handler, detect exception and call appropriate asserts. 在这样的存根setTimeout您可以装饰超时处理程序,检测异常并调用适当的断言。

NOTE, this is last resort solution - your app code is broken and should be fixed to properly propagate errors, not only for testing but ... well, to be good code. 注意,这是万不得已的解决方案-您的应用代码已损坏,应该进行修复以正确传播错误,不仅用于测试,而且……要成为好的代码。

Pseudocode example: 伪代码示例:

 it('test', (done) => { const originalSetTimeout = setTimeout; setTimeout = (callback, timeout) => { originalSetTimeout(() => { try { callback(); } catch(error) { // CONGRATS, you've intercepted exception // in _SOME_ setTimeout handler } }, timeout) } yourTestCodeThatTriggersErrorInSomeSetTimeoutCallback(done); }) 

NOTE2: I intentionally didn't wrote proper async cleanup code, it's a homework. 注意2:我故意没有编写适当的异步清理代码,这是一项家庭作业。 Again, see sinon.js and its sandbox 再次,请参见sinon.js及其sandbox

NOTE3: It will catch all setTimeout calls during test duration. 注意3:它将在测试期间捕获所有setTimeout调用。 Beware, there are dragons. 当心,有龙。

From Chai documentation : 柴文件

When no arguments are provided, .throw invokes the target function and asserts that an error is thrown. 如果未提供任何参数,则.throw会调用目标函数并断言已引发错误。

So you could something like 所以你可以像

expect(Matcher(1).case(1, () => {})).to.throw

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM