我如何通过 Mocha 的模拟承诺进行测试?
How can I test through a mocked promise with Mocha?
我的node.js代码是:
function getPatientNotificationNumbers(patientId) {
patientId = patientId && patientId.toString();
var sql = "SELECT * FROM [notification_phone_number] ";
sql += "WHERE patient_id = " + escapeSql(patientId);
return sqlServer.query(sql).then(function(results) {
var phoneNumbers = _.map(results[0], function (result) {
var obj = {
id: result.id,
phoneNumber: result.phone_number,
isPrimary: result.is_primary,
isVerified: result.is_verified,
patientId: result.patient_id
}
return obj;
});
return phoneNumbers;
});
}
非常简单明了。我想测试的是,这个函数的 return 正确解析后,是一个匹配该格式的 phoneNumbers
数组。
sqlServer
在上面 require
,我在这个文件中有很多东西 require
。为了消除它们,我使用了 mockery
,这似乎非常棒。
到目前为止,这是我的测试:
before(function() {
deferred = Q.defer();
mockery.enable();
moduleConfig.init();
mockery.registerSubstitute('moment', moment);
mockery.registerAllowable('../../util');
mockStubs.sqlServer = {
query: sinon.stub().returns(deferred.promise)
}
mockery.registerMock('../../db/sqlserver', mockStubs.sqlServer);
methods = require('../../../rpc/patient/methods');
});
beforeEach(function() {
deferred = Q.defer();
})
it('should get the patient notification numbers', function(done) {
// sinon.spy(sqlServer, 'query').and.returnValue(deferred.promise);
deferred.resolve('here we go');
methods.getPatientNotificationNumbers(1).then(function(result) {
console.log(result);
done();
});
});
但是,它在我的代码中永远不会超过 sqlServer.query
。所以 results
毫无意义。我也试过类似的东西:
response = methods.getPatientNotificationNumbers(1)
但是当我console.log(response)
的时候,基本上是{state: 'pending'}
,我猜这是一个悬而未决的承诺。
所以我到处都是,我愿意使用任何使事情变得简单的库。我没有和 mockery
、sinon
或其他任何人结婚。任何建议都会有所帮助。
谢谢!
在我看来,您的意图是测试您通过 .then()
方法传递的功能,而不是 sqlserver
库或实际承诺 returns。我们可以假设这些已经过测试。
如果是这种情况,您可以简单地分解出该函数(从 SQL 结果中提取 phone 数字的函数)并独立测试它。
您的代码将变成:
var getNumbers = require('./getNumbers');
function getPatientNotificationNumbers(patientId) {
patientId = patientId && patientId.toString();
var sql = "SELECT * FROM [notification_phone_number] ";
sql += "WHERE patient_id = " + escapeSql(patientId);
return sqlServer.query(sql).then(getNumbers);
}
... 而您的 ./getNumbers.js
文件将类似于:
function getNumbers(results) {
var phoneNumbers = _.map(results[0], function (result) {
var obj = {
id: result.id,
phoneNumber: result.phone_number,
isPrimary: result.is_primary,
isVerified: result.is_verified,
patientId: result.patient_id
}
return obj;
});
return phoneNumbers;
}
module.exports = getNumbers;
现在可以独立测试了:
var getNumbers = require('../path/to/getNumbers');
...
it('should get the patient notification numbers', function(done) {
var sampleResults = [ ... ]; // sample results from sqlServer
var phoneNumbers = getNumbers(sampleResults);
assert(...); // make what ever assertions you want
});
我会缩小一点并考虑测试策略。假设目标是在 sql 服务器之上测试模型层(方法)。它可以在不关闭 sql 服务器的情况下完成。您的测试套件层可以有一组 util 方法来创建、初始化和删除可以从 before、beforeEach 等调用的数据库。
这样做的优点:
- 测试真实的产品代码路径更好(比存根代码路径)。
- 如果您怀疑底层错误会产生噪声,则存根更好。 sql服务器层可能稳定。
- 模型层似乎很简单,不需要单独测试。
- 如果您尝试在模型层中测试 sql服务器故障处理,那么存根是有意义的。然后存根层可以伪造这样的错误——在模型代码中练习错误路径。
这是基于对您问题的有限认识。如果还有更多的话。请分享,我们可以从那里拿走。
所以另一种方法是使用rewire and deride的组合。
var should = require('should-promised');
var rewire = require('rewire');
var Somefile = rewire('./path/to/file');
var deride = require('deride');
var sut, mockSql;
before(function() {
mockSql = deride.stub(['query']);
Somefile.__set__('sqlServer', mockSql);
sut = new Somefile();
});
describe('getting patient notification numbers', function() {
beforeEach(function() {
mockSql.setup.query.toResolveWith(fakeSqlResponse);
});
it('resolves the promise', function() {
sut.getPatientNotificationNumbers(id).should.be.fulfilledWith(expectedPhoneNumbers);
});
it('queries Sql', function(done) {
sut.getPatientNotificationNumbers(id).then(function() {
mockSql.expect.query.called.once();
done();
});
});
});
这意味着您无需更改生产代码,您可以使用如下方式轻松开始测试不满意的路径:
it('handles Sql errors', function(done) {
mockSql.setup.query.toRejectWith(new Error('Booom'));
sut.getPatientNotificationNumbers(id)
.then(function(){
done('should not have resolved');
})
.catch(function(e) {
e.should.be.an.Error;
e.message.should.eql('Booom');
done();
});
});
或者更简洁:
it('handles Sql errors', function(done) {
mockSql.setup.query.toRejectWith(new Error('Booom'));
sut.getPatientNotificationNumbers(id).should.be.rejectedWith(/Booom/);
});
我的node.js代码是:
function getPatientNotificationNumbers(patientId) {
patientId = patientId && patientId.toString();
var sql = "SELECT * FROM [notification_phone_number] ";
sql += "WHERE patient_id = " + escapeSql(patientId);
return sqlServer.query(sql).then(function(results) {
var phoneNumbers = _.map(results[0], function (result) {
var obj = {
id: result.id,
phoneNumber: result.phone_number,
isPrimary: result.is_primary,
isVerified: result.is_verified,
patientId: result.patient_id
}
return obj;
});
return phoneNumbers;
});
}
非常简单明了。我想测试的是,这个函数的 return 正确解析后,是一个匹配该格式的 phoneNumbers
数组。
sqlServer
在上面 require
,我在这个文件中有很多东西 require
。为了消除它们,我使用了 mockery
,这似乎非常棒。
到目前为止,这是我的测试:
before(function() {
deferred = Q.defer();
mockery.enable();
moduleConfig.init();
mockery.registerSubstitute('moment', moment);
mockery.registerAllowable('../../util');
mockStubs.sqlServer = {
query: sinon.stub().returns(deferred.promise)
}
mockery.registerMock('../../db/sqlserver', mockStubs.sqlServer);
methods = require('../../../rpc/patient/methods');
});
beforeEach(function() {
deferred = Q.defer();
})
it('should get the patient notification numbers', function(done) {
// sinon.spy(sqlServer, 'query').and.returnValue(deferred.promise);
deferred.resolve('here we go');
methods.getPatientNotificationNumbers(1).then(function(result) {
console.log(result);
done();
});
});
但是,它在我的代码中永远不会超过 sqlServer.query
。所以 results
毫无意义。我也试过类似的东西:
response = methods.getPatientNotificationNumbers(1)
但是当我console.log(response)
的时候,基本上是{state: 'pending'}
,我猜这是一个悬而未决的承诺。
所以我到处都是,我愿意使用任何使事情变得简单的库。我没有和 mockery
、sinon
或其他任何人结婚。任何建议都会有所帮助。
谢谢!
在我看来,您的意图是测试您通过 .then()
方法传递的功能,而不是 sqlserver
库或实际承诺 returns。我们可以假设这些已经过测试。
如果是这种情况,您可以简单地分解出该函数(从 SQL 结果中提取 phone 数字的函数)并独立测试它。
您的代码将变成:
var getNumbers = require('./getNumbers');
function getPatientNotificationNumbers(patientId) {
patientId = patientId && patientId.toString();
var sql = "SELECT * FROM [notification_phone_number] ";
sql += "WHERE patient_id = " + escapeSql(patientId);
return sqlServer.query(sql).then(getNumbers);
}
... 而您的 ./getNumbers.js
文件将类似于:
function getNumbers(results) {
var phoneNumbers = _.map(results[0], function (result) {
var obj = {
id: result.id,
phoneNumber: result.phone_number,
isPrimary: result.is_primary,
isVerified: result.is_verified,
patientId: result.patient_id
}
return obj;
});
return phoneNumbers;
}
module.exports = getNumbers;
现在可以独立测试了:
var getNumbers = require('../path/to/getNumbers');
...
it('should get the patient notification numbers', function(done) {
var sampleResults = [ ... ]; // sample results from sqlServer
var phoneNumbers = getNumbers(sampleResults);
assert(...); // make what ever assertions you want
});
我会缩小一点并考虑测试策略。假设目标是在 sql 服务器之上测试模型层(方法)。它可以在不关闭 sql 服务器的情况下完成。您的测试套件层可以有一组 util 方法来创建、初始化和删除可以从 before、beforeEach 等调用的数据库。 这样做的优点:
- 测试真实的产品代码路径更好(比存根代码路径)。
- 如果您怀疑底层错误会产生噪声,则存根更好。 sql服务器层可能稳定。
- 模型层似乎很简单,不需要单独测试。
- 如果您尝试在模型层中测试 sql服务器故障处理,那么存根是有意义的。然后存根层可以伪造这样的错误——在模型代码中练习错误路径。
这是基于对您问题的有限认识。如果还有更多的话。请分享,我们可以从那里拿走。
所以另一种方法是使用rewire and deride的组合。
var should = require('should-promised');
var rewire = require('rewire');
var Somefile = rewire('./path/to/file');
var deride = require('deride');
var sut, mockSql;
before(function() {
mockSql = deride.stub(['query']);
Somefile.__set__('sqlServer', mockSql);
sut = new Somefile();
});
describe('getting patient notification numbers', function() {
beforeEach(function() {
mockSql.setup.query.toResolveWith(fakeSqlResponse);
});
it('resolves the promise', function() {
sut.getPatientNotificationNumbers(id).should.be.fulfilledWith(expectedPhoneNumbers);
});
it('queries Sql', function(done) {
sut.getPatientNotificationNumbers(id).then(function() {
mockSql.expect.query.called.once();
done();
});
});
});
这意味着您无需更改生产代码,您可以使用如下方式轻松开始测试不满意的路径:
it('handles Sql errors', function(done) {
mockSql.setup.query.toRejectWith(new Error('Booom'));
sut.getPatientNotificationNumbers(id)
.then(function(){
done('should not have resolved');
})
.catch(function(e) {
e.should.be.an.Error;
e.message.should.eql('Booom');
done();
});
});
或者更简洁:
it('handles Sql errors', function(done) {
mockSql.setup.query.toRejectWith(new Error('Booom'));
sut.getPatientNotificationNumbers(id).should.be.rejectedWith(/Booom/);
});