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

Posted

技术标签:

【中文标题】有没有办法让 Chai 使用异步 Mocha 测试?【英文标题】:Is there a way to get Chai working with asynchronous Mocha tests? 【发布时间】:2012-06-29 10:51:04 【问题描述】:

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

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 这样的东西的所有优雅,它变得非常笨重......

【问题讨论】:

问题在于浏览器端的摩卡咖啡。有关这方面的信息,请参阅 github.com/visionmedia/mocha/pull/278。 截至 2020 年,您应该看看 chai-as-promised 插件... 【参考方案1】:

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

您看到显示的捕获异常是在节点下使用process.on('uncaughtException') 或在浏览器中使用window.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 );
  

【讨论】:

在我意识到我抱怨的那一点 (setTimeout) 实际上来自我的问题之后,我刚刚删除了我之前的评论。对不起!! 上面的答案似乎是错误的。失败的期望将立即抛出并以有意义的错误停止测试,不需要复杂的 try/catch。我刚刚通过浏览器测试对其进行了测试。 我一直在努力解决这个问题,发现这篇博文非常有帮助:staxmanade.com/2015/11/… @RichardForrester,非常有帮助的帖子。谢谢!使用 Promises 进行检查可以极大地简化代码。但它必须与承诺(不是任何异步功能)。 只是想提醒后代,这个确切的问题发生在 Vue nexttick()(它是 promise 的包装器)并且可以以相同的方式处理。【参考方案2】:

这是我对 ES6/ES2015 Promise 和 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。正如您所指出的,它不是必需的。【参考方案3】:

如果您愿意,请尝试Chai as Promised + Q,这样可以:

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

【讨论】:

【参考方案4】:

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

始终以if (err) done(err); 开始测试 始终以done() 结束测试。

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

这是link to the discussion in the mailing list。

【讨论】:

您链接到的讨论是关于服务器端 chai 和 mocha。发帖人询问的是浏览器端 mocha 和 chai。 这不是同一个问题。在这个问题中用作示例的setTimeout 函数在其回调中没有任何错误。【参考方案5】:

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

首先安装check-chai包:

npm install --save check-chai

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

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');
    );

  );

);

根据Is there a way to get Chai working with asynchronous Mocha tests?,我将其作为 NPM 包发布。

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

【讨论】:

【参考方案6】:

试试 chaiAsPromised!除了出色的命名之外,您还可以使用以下语句:

expect(asyncToResultingValue()).to.eventually.equal(true)

Can confirm,非常适合 Mocha + Chai。

https://github.com/domenic/chai-as-promised

【讨论】:

【参考方案7】:

与Jean Vincent's answer 非常相关并受其启发,我们使用了一个类似于他的check 函数的辅助函数,但我们将其称为eventually(这有助于它与 chai-as- 的命名约定相匹配)承诺)。它返回一个函数,该函数接受任意数量的参数并将它们传递给原始回调。这有助于消除测试中额外的嵌套函数块,并允许您处理任何类型的异步回调。这里是用 ES2015 写的:

function eventually(done, fn) 
  return (...args) => 
    try 
      fn(...args);
      done();
     catch (err) 
      done(err);
    
  ;
;

示例用法:

describe("my async test", function() 
  it("should fail", function(done) 
    setTimeout(eventually(done, (param1, param2) => 
      assert.equal(param1, "foo");   // this should pass
      assert.equal(param2, "bogus"); // this should fail
    ), 100, "foo", "bar");
  );
);

【讨论】:

【参考方案8】:

我知道有很多重复的答案和建议的包来解决这个问题,但是我还没有看到上面的简单解决方案为这两个用例提供了简洁的模式。我将其发布为其他希望复制意大利面的综合答案:

事件回调

function expectEventCallback(done, fn) 
  return function() 
    try  fn(...arguments); 
    catch(error)  return done(error); 
    done();
  ;

节点样式回调

function expectNodeCallback(done, fn) 
  return function(err, ...args) 
    if (err)  return done(err); 
    try  fn(...args); 
    catch(error)  return done(error); 
    done();
  ;

示例用法

it('handles event callbacks', function(done) 
  something.on('event', expectEventCallback(done, (payload) => 
    expect(payload).to.have.propertry('foo');
  ));
);

it('handles node callbacks', function(done) 
  doSomething(expectNodeCallback(done, (payload) => 
    expect(payload).to.have.propertry('foo');
  ));
);

【讨论】:

【参考方案9】:

根据@richardforrester http://staxmanade.com/2015/11/testing-asyncronous-code-with-mochajs-and-es7-async-await/ 提供的这个链接,如果省略done 参数,describe 可以使用返回的Promise。

唯一的缺点是那里必须有一个 Promise,而不是任何异步函数(你可以用 Promise 包装它)。但在这种情况下,代码可以大大减少。

它考虑了初始 funcThatReturnsAPromise 函数或期望中的失败:

it('should test Promises', function ()  // <= done removed
    return testee.funcThatReturnsAPromise('name': 'value') // <= return added
        .then(response => expect(response).to.have.property('ok', 1));
);

【讨论】:

【参考方案10】:

我解决了将try/catch 提取到函数的问题。

function asyncExpect(test, done)
    try
        test();
        done();
     catch(error)
        done(error);
    

然后在it()我打电话:

it('shall update a host', function (done) 
            testee.insertHost(_id: 'host_id')
                .then(response => 
                    asyncExpect(() => 
                        expect(response).to.have.property('ok', 1);
                        expect(response).to.have.property('nModified', 1);
                    , done);
                );

        );

它也是可调试的。

【讨论】:

【参考方案11】:

测试和异步期间的计时器听起来很粗糙。有一种基于 Promise 的方法可以做到这一点。

const sendFormResp = async (obj) => 
    const result = await web.chat.postMessage(
        text: 'Hello world!',
    );
   return result

此异步函数使用 Web 客户端(在本例中为 Slacks SDK)。 SDK 负责 API 调用的异步特性并返回有效负载。然后,我们可以通过针对异步承诺中返回的对象运行 expect 在 chai 中测试有效负载。

describe("Slack Logic For Working Demo Environment", function (done) 
    it("Should return an object", () => 
        return sdkLogic.sendFormResp(testModels.workingModel).then(res => 
            expect(res).to.be.a("Object");
        )
    )
);

【讨论】:

【参考方案12】:

更简单的方法是使用wait-for-expect 库。

const waitForExpect = require("wait-for-expect")

test("it waits for the number to change", async () => 
  let numberToChange = 10;

  setTimeout(() => 
    numberToChange = 100;
  , randomTimeout);

  await waitForExpect(() => 
    expect(numberToChange).toEqual(100);
  );
);

【讨论】:

【参考方案13】:

对我来说非常有效的 icm Mocha / Chai 是来自诗乃图书馆的 fakeTimer。 只需在必要时提前测试中的计时器即可。

var sinon = require('sinon');
clock = sinon.useFakeTimers();
// Do whatever. 
clock.tick( 30000 ); // Advances the JS clock 30 seconds.

具有更快完成测试的额外好处。

【讨论】:

我确实发现自己现在在测试异步代码时主要使用这样的解决方案。最好有“完成”回调 Mocha(如上面 Jean Vincent 的回答所示),但是当你不使用它时,测试通常更容易编写。【参考方案14】:

您也可以使用域模块。例如:

var domain = require('domain').create();

domain.run(function()

    // place you code here
);

domain.on('error',function(error)
    // do something with error or simply print it
);

【讨论】:

以上是关于有没有办法让 Chai 使用异步 Mocha 测试?的主要内容,如果未能解决你的问题,请参考以下文章

我如何使用 Mocha 和 Chai 对 Node 创建的端点进行异步测试?

Mocha / Chai expect.to.throw 没有捕捉到抛出的错误

当 app 是 Promise 时启动 mocha chai 测试

Mocha 异步测试超时的解决方法 [重复]

Mocha - Chai Unit Terst 报告生成 - NodeJS

使用 Mocha/Chai 测试复杂的 React 组件