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


81

我正在使用浏览器运行程序在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)

因此,显然是在捕获错误,只是没有正确显示它。任何想法如何做到这一点?我想我可以用一个错误对象调用“完成”,但是然后我失去了像柴一样的优雅,它变得笨拙...


问题出在浏览器端的摩卡咖啡上。有关此信息,请参见github.com/visionmedia/mocha/pull/278
艾略特·福斯特

从2020年开始,您应该看看chai-as-promised插件...
Elmar Zander

Answers:


96

您的异步测试会针对失败的状态生成一个异常,该异常expect()无法捕获,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)实际上是我的问题之后,我才删除了先前的评论。抱歉!!
Thomas Parslow 2013年

2
上面的答案似乎是错误的。失败的期望将立即抛出并以有意义的错误停止测试,无需复杂的try / catch。我刚刚通过浏览器测试对其进行了测试。
Offirmo

3
我在这个问题上苦苦挣扎,发现此博客文章非常有帮助:staxmanade.com/2015/11/…– RichardForrester 2015
20:27

1
@RichardForrester,非常有用的帖子。谢谢!为了使此检查与Promises配合使用,可极大地简化代码。但这必须保证(不是任何异步功能)。
Pedro R.

1
只是想提醒一下,这个确切的问题发生在Vue nexttick()(这是对promise的包装)中,可以用相同的方式处理。
伊莱·艾伯特

20

这是我对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')
  }))
})

@PedroR。我更改为从诺言测试中删除完成。正如您所指出的,它不是必需的。
RichardForrester '16

13

如果您愿意,请尝试Chai as Promised + Q,它允许这样的事情:

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

2

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

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

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

这是邮件列表中讨论链接


1
您链接到的讨论是关于服务器端chai和mocha的。海报询问浏览器端的摩卡咖啡和彩。
艾略特·福斯特

那不是同一个问题。setTimeout在此问题中用作示例的函数的回调没有任何错误。
Sylvain B,

1

我已经发布了可解决此问题的软件包。

首先安装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');
    });

  });

});

Per是否有办法让Chai使用异步Mocha测试?我将此发布为NPM软件包。

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



1

让·文森特Jean Vincent)的答案息息相关,我们采用了与他的功能类似的帮助器功能check,但我们称之为辅助功能eventually(这有助于将其与chai-as-promise的命名约定相匹配)。它返回一个接受任意数量参数的函数,并将其传递给原始回调。这有助于消除测试中额外的嵌套功能块,并允许您处理任何类型的异步回调。这是在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");
  });
});

1

我知道有很多重复的答案和建议的解决方案,但是我没有看到上面的简单解决方案为这两种用例提供​​了简洁的模式。我将其发布为其他希望复制粘贴的合并答案:

事件回调

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

0

根据@richardforrester提供的链接http://staxmanade.com/2015/11/testing-asyncronous-code-with-mochajs-and-es7-async-await/,如果您忽略了已完成的操作,则可以使用返回的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));
});

0

我解决了提取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);
                });

        });

它也是可调试的。


0

测试和异步期间的计时器听起来很粗糙。有一种方法可以采用基于承诺的方法。

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");
        })
    })
});

-2

icm Mocha / Chai对我来说非常有效的是Sinon图书馆的falseTimer。只需在必要时提前计时器即可。

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

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


1
在测试异步代码时,我肯定已经发现自己现在主要使用这种解决方案。最好有“完成”的回调Mocha(如上面的Jean Vincent的答案所示),但是当您不使用测试时,通常更容易编写测试。
Thomas Parslow 2015年

-2

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

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
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.