如何对调用另一个 returns 承诺的函数进行单元测试?
How to unit test a function which calls another that returns a promise?
我有一个使用 express 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);
});
}
};
它称我的服务为 returns 承诺。一切正常,但我在尝试对其进行单元测试时遇到了麻烦。
基本上,我想确保根据我的服务 returns,我使用正确的状态代码和正文刷新响应。
所以对于 mocha 和 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();
});
当我正在测试的函数 returns 本身就是一个承诺时,我已经测试了类似的场景,因为我可以在那时挂钩我的断言。
我也试过用 Promise 包装 controller.findAll 并从 response.send 解决它,但它也没有用。
这里的想法是让 service.findAll()
returns 可以在测试代码中访问而无需调用 service
。据我所知,您可能使用的 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;
});
您应该将断言部分移动到 res.send
方法中,以确保在断言之前完成所有异步任务:
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
包含在您的模块中,并且根本没有暴露依赖关系。我觉得目标不应该是找到一种方法来测试你的代码,而是找到一个最优的设计。
IMO 目标是找到一种公开 service
的方法,以便您的测试可以提供存根实现,从而可以隔离、同步地测试 findAll
的逻辑。
一种方法是使用像 mockery
或 rewire
这样的库。两者都相当容易使用,(根据我的经验,随着测试套件和模块数量的增长,嘲笑开始退化并变得非常难以维护)它们将允许您通过提供自己的服务对象来修补 var service = require('./category.service');
它自己的 findAll
定义。
另一种方法是重新设计您的代码,以某种方式将 service
公开给调用者。这将允许您的调用者(单元测试)提供自己的 service
存根。
一个简单的方法是导出函数构造函数而不是对象。
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();
现在测试可以为 service
创建存根并将其提供给 ServiceConstructor
以执行 findAll
方法。完全不需要异步测试。
我有一个使用 express 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);
});
}
};
它称我的服务为 returns 承诺。一切正常,但我在尝试对其进行单元测试时遇到了麻烦。
基本上,我想确保根据我的服务 returns,我使用正确的状态代码和正文刷新响应。
所以对于 mocha 和 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();
});
当我正在测试的函数 returns 本身就是一个承诺时,我已经测试了类似的场景,因为我可以在那时挂钩我的断言。
我也试过用 Promise 包装 controller.findAll 并从 response.send 解决它,但它也没有用。
这里的想法是让 service.findAll()
returns 可以在测试代码中访问而无需调用 service
。据我所知,您可能使用的 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;
});
您应该将断言部分移动到 res.send
方法中,以确保在断言之前完成所有异步任务:
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
包含在您的模块中,并且根本没有暴露依赖关系。我觉得目标不应该是找到一种方法来测试你的代码,而是找到一个最优的设计。
IMO 目标是找到一种公开 service
的方法,以便您的测试可以提供存根实现,从而可以隔离、同步地测试 findAll
的逻辑。
一种方法是使用像 mockery
或 rewire
这样的库。两者都相当容易使用,(根据我的经验,随着测试套件和模块数量的增长,嘲笑开始退化并变得非常难以维护)它们将允许您通过提供自己的服务对象来修补 var service = require('./category.service');
它自己的 findAll
定义。
另一种方法是重新设计您的代码,以某种方式将 service
公开给调用者。这将允许您的调用者(单元测试)提供自己的 service
存根。
一个简单的方法是导出函数构造函数而不是对象。
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();
现在测试可以为 service
创建存根并将其提供给 ServiceConstructor
以执行 findAll
方法。完全不需要异步测试。