有没有办法让 Chai 使用异步 Mocha 测试?

IT技术 javascript unit-testing mocha.js
2021-03-03 19:36:05

我正在使用 Browser Runner 在 Mocha 中运行一些异步测试,并且我正在尝试使用 Chai 的期望样式断言:

window.expect = chai.expect;
describe('my test', function() {
  it('should do something', function (done) {
    setTimeout(function () {
      expect(true).to.equal(false);
    }, 100);
  }
}

这不会给我正常的失败断言消息,而是我得到:

Error: the string "Uncaught AssertionError: expected true to equal false" was thrown, throw an Error :)
    at Runner.fail (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3475:11)
    at Runner.uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3748:8)
    at uncaught (http://localhost:8000/tests/integration/mocha/vendor/mocha.js:3778:10)

所以它显然是在捕捉错误,只是没有正确显示它。任何想法如何做到这一点?我想我可以用一个错误对象调用“完成”,但随后我失去了像 Chai 这样的东西的所有优雅,它变得非常笨重......

6个回答

您的异步测试会在失败时生成一个异常,该异常expect()无法被捕获,it()因为异常是在it()的范围之外抛出的。

您看到的捕获异常是使用process.on('uncaughtException')under node 或 usingwindow.onerror()在浏览器中捕获的。

要解决此问题,您需要在调用的异步函数中捕获异常setTimeout(),以便done()将异常作为第一个参数进行调用您还需要在done()不带参数的情况下调用以指示成功,否则 mocha 会报告超时错误,因为您的测试函数永远不会发出已完成的信号:

window.expect = chai.expect;

describe( 'my test', function() {
  it( 'should do something', function ( done ) {
    // done() is provided by it() to indicate asynchronous completion
    // call done() with no parameter to indicate that it() is done() and successful
    // or with an error to indicate that it() failed
    setTimeout( function () {
      // Called from the event loop, not it()
      // So only the event loop could capture uncaught exceptions from here
      try {
        expect( true ).to.equal( false );
        done(); // success: call done with no parameter to indicate that it() is done()
      } catch( e ) {
        done( e ); // failure: call done with an error Object to indicate that it() failed
      }
    }, 100 );
    // returns immediately after setting timeout
    // so it() can no longer catch exception happening asynchronously
  }
}

在你的所有测试用例上这样做很烦人,而不是 DRY,所以你可能想提供一个函数来为你做这件事。让我们调用这个函数check()

function check( done, f ) {
  try {
    f();
    done();
  } catch( e ) {
    done( e );
  }
}

现在,check()您可以按如下方式重写异步测试:

window.expect = chai.expect;

describe( 'my test', function() {
  it( 'should do something', function( done ) {
    setTimeout( function () {
      check( done, function() {
        expect( true ).to.equal( false );
      } );
    }, 100 );
  }
}
@RichardForrester,非常有帮助的帖子。谢谢!为了让这个检查与 Promises 一起工作,难以置信地简化了代码。但它必须与Promise(不是任何异步功能)。
2021-04-30 19:36:05
在我意识到我抱怨的那一点 (setTimeout) 实际上来自我的问题之后,我刚刚删除了我之前的评论。对不起!!
2021-05-02 19:36:05
上面的答案似乎是错误的。失败的期望会立即抛出并以有意义的错误停止测试,不需要复杂的 try/catch。我现在刚刚通过浏览器测试对其进行了测试。
2021-05-02 19:36:05
只是想为后代说明这个确切的问题发生在 Vue nexttick()(它是 promise 的包装器)并且可以以相同的方式处理。
2021-05-13 19:36:05
我在这个问题上苦苦挣扎,发现这篇博文非常有帮助:staxmanade.com/2015/11/...
2021-05-19 19:36:05

这是我对 ES6/ES2015 promises 和 ES7/ES2016 async/await 的通过测试。希望这为任何研究此主题的人提供了一个很好的更新答案:

import { expect } from 'chai'

describe('Mocha', () => {
  it('works synchronously', () => {
    expect(true).to.equal(true)
  })

  it('works ansyncronously', done => {
    setTimeout(() => {
      expect(true).to.equal(true)
      done()
    }, 4)
  })

  it('throws errors synchronously', () => {
    return true
    throw new Error('it works')
  })

  it('throws errors ansyncronously', done => {
    setTimeout(() => {
      return done()
      done(new Error('it works'))
    }, 4)
  })

  it('uses promises', () => {
    var testPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello')
      }, 4)
    })

    testPromise.then(result => {
      expect(result).to.equal('Hello')
    }, reason => {
      throw new Error(reason)
    })
  })

  it('uses es7 async/await', async (done) => {
    const testPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello')
      }, 4)
    })

    try {
      const result = await testPromise
      expect(result).to.equal('Hello')
      done()
    } catch(err) {
      done(err)
    }
  })

  /*
  *  Higher-order function for use with async/await (last test)
  */
  const mochaAsync = fn => {
    return async (done) => {
      try {
        await fn()
        done()
      } catch (err) {
        done(err)
      }
    }
  }

  it('uses a higher order function wrap around async', mochaAsync(async () => {
    const testPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('Hello')
      }, 4)
    })

    expect(await testPromise).to.equal('Hello')
  }))
})
@Pedro R. 我更改为从 promise 测试中删除 done。正如你所指出的,它是不需要的。
2021-05-14 19:36:05

如果你喜欢 promise,试试Chai as Promised + Q,它允许这样的事情:

doSomethingAsync().should.eventually.equal("foo").notify(done);

我在 Mocha 邮件列表中问了同样的问题。他们基本上告诉我:用 Mocha 和 Chai 编写异步测试:

  • 总是开始测试 if (err) done(err);
  • 总是以 结束测试done()

它解决了我的问题,并且没有改变我中间的一行代码(Chai 期望等)。setTimout不是进行异步测试的方法。

这是邮件列表中讨论链接

您链接到的讨论是关于服务器端 chai 和 mocha。海报询问浏览器端的mocha 和 chai。
2021-05-02 19:36:05
那不是同一个问题。setTimeout在此问题中用作示例函数在其回调中没有任何错误。
2021-05-16 19:36:05

我发布了一个包来解决这个问题。

首先安装check-chai软件包:

npm install --save check-chai

然后在您的测试中,使用chai.use(checkChai);然后使用chai.checkhelper 函数,如下所示:

var chai = require('chai');
var dirtyChai = require('dirty-chai');
var checkChai = require('check-chai');
var expect = chai.expect;
chai.use(dirtyChai);
chai.use(checkChai);

describe('test', function() {

  it('should do something', function(done) {

    // imagine you have some API call here
    // and it returns (err, res, body)
    var err = null;
    var res = {};
    var body = {};

    chai.check(done, function() {
      expect(err).to.be.a('null');
      expect(res).to.be.an('object');
      expect(body).to.be.an('object');
    });

  });

});

Per有没有办法让 Chai 使用异步 Mocha 测试?我将其发布为 NPM 包。

请参阅https://github.com/niftylettuce/check-chai了解更多信息。