如何unit testing一个函数调用另一个返回一个承诺?

我有一个使用快递4的node.js应用程序,这是我的控制器:

var service = require('./category.service'); module.exports = { findAll: (request, response) => { service.findAll().then((categories) => { response.status(200).send(categories); }, (error) => { response.status(error.statusCode || 500).json(error); }); } }; 

它呼吁我的服务返回一个承诺。 一切正常,但我试图unit testing时遇到了麻烦。

基本上,我想确保根据我的服务返回,我用正确的状态代码和正文刷新响应。

所以摩卡和sinon看起来像这样:

 it('Should call service to find all the categories', (done) => { // Arrange var expectedCategories = ['foo', 'bar']; var findAllStub = sandbox.stub(service, 'findAll'); findAllStub.resolves(expectedCategories); var response = { status: () => { return response; }, send: () => {} }; sandbox.spy(response, 'status'); sandbox.spy(response, 'send'); // Act controller.findAll({}, response); // Assert expect(findAllStub.called).to.be.ok; expect(findAllStub.callCount).to.equal(1); expect(response.status).to.be.calledWith(200); // not working expect(response.send).to.be.called; // not working done(); }); 

当我正在testing的函数返回自己的承诺时,我已经testing了类似的场景,因为我可以在那里挂断我的断言。

我也试图用promise来包装controller.findAll,并从response.send解决它,但它也不工作。

您应该将assert部分移到res.send方法中,以确保所有asynchronous任务在断言之前完成:

 var response = { status: () => { return response; }, send: () => { try { // Assert expect(findAllStub.called).to.be.ok; expect(findAllStub.callCount).to.equal(1); expect(response.status).to.be.calledWith(200); // not working // expect(response.send).to.be.called; // not needed anymore done(); } catch (err) { done(err); } }, }; 

这里的想法是让service.findAll()在testing的代码内部可以访问,而不用调用service 。 据我sinon-as-promised ,你可能使用的sinon-as-promised不允许这么做。 所以我只是使用了原生Promise (希望你的节点版本不太旧)。

 const aPromise = Promise.resolve(expectedCategories); var findAllStub = sandbox.stub(service, 'findAll'); findAllStub.returns(aPromise); // response = { .... } controller.findAll({}, response); aPromise.then(() => { expect(response.status).to.be.calledWith(200); expect(response.send).to.be.called; }); 

当代码难以testing时,可能表明可能有不同的devise可能性来探索,从而促进简单的testing。 跳出来的是service被封装在你的模块中,依赖关系根本没有暴露。 我觉得目标不应该是find一个方法来testing你的代码,而是find一个最佳的devise。

国际海事组织的目标是find一种方法来公开service以便您的testing可以提供一个桩的实现,以便findAll的逻辑可以隔离,同步testing。

一种方法是使用像mockeryrewire图书馆。 两者都相当容易使用(根据我的经验,嘲讽开始降级,并且随着testing套件和模块数量的增长而变得非常难以维护)。它们将允许您修补var service = require('./category.service'); 通过提供您自己的服务对象与自己的findAll定义。

另一种方法是重新构build你的代码,以某种方式向调用者公开service 。 这将允许你的调用者(unit testing)提供自己的service存根。

一个简单的方法就是导出一个函数contstructor而不是一个对象。

 module.exports = (userService) => { // default to the required service this.service = userService || service; this.findAll = (request, response) => { this.service.findAll().then((categories) => { response.status(200).send(categories); }, (error) => { response.status(error.statusCode || 500).json(error); }); } }; var ServiceConstructor = require('yourmodule'); var service = new ServiceConstructor(); 

现在testing可以为service创build一个存根,并将其提供给ServiceConstructor以执行findAll方法。 完全不需要进行asynchronoustesting。