Как я могу заблокировать Обещание так, чтобы мой тест мог выполняться синхронно?
Я пытаюсь выполнить модульное тестирование модуля, заглушив одну из его зависимостей, в данном случае UserManager
Упрощенная версия модуля выглядит следующим образом:
// CodeHandler
module.exports = function(UserManager) {
return {
oAuthCallback: function(req, res) {
var incomingCode = req.query.code;
var clientKey = req.query.key;
UserManager.saveCode(clientKey, incomingCode)
.then(function(){
res.redirect('https://test.tes');
}).catch(function(err){
res.redirect('back');
}
);
}
};
};
Я заглушаю UserManager's saveCode
функция, которая возвращает Promise
такой, что он возвращает разрешенное обещание, но когда я assert
тот res.redirect
был вызван, увы, во время утверждения res.redirect
еще не был вызван.
Упрощенная версия модульного теста:
// test
describe('CodeHandler', function() {
var req = {
query: {
code: 'test-code',
key: 'test-state'
}
};
var res = {
redirect: function() {}
};
var expectedUrl = 'https://test.tes';
var ch;
beforeEach(function() {
sinon.stub(UserManager, 'saveCode').returns(
new RSVP.Promise(function(resolve, reject){
resolve();
})
);
sinon.stub(res, 'redirect');
ch = CodeHandler(UserManager);
});
afterEach(function() {
UserManager.saveCode.restore();
res.redirect.restore();
});
it('redirects to the expected URL', function(){
ch.oAuthCallback(req, res);
assert(res.redirect.calledWith(expectedUrl));
})
});
Как правильно заблокировать обещание, чтобы тестируемый метод работал синхронно?
2 ответа
Я разработал решение, используя sinon-stub-promise
,
describe('CodeHandler', function() {
var req = {
query: {
code: 'test-code',
key: 'test-state'
}
};
var ch;
var promise;
var res = {
redirect: function() {}
};
beforeEach(function() {
promise = sinon.stub(UserManager, 'saveCode').returnsPromise();
ch = CodeHandler(UserManager);
sinon.stub(res, 'redirect');
});
afterEach(function() {
UserManager.saveCode.restore();
res.redirect.restore();
});
describe('can save code', function() {
var expectedUrl = 'https://test.tes';
beforeEach(function() {
promise.resolves();
});
it('redirects to the expected URL', function(){
ch.oAuthCallback(req, res);
assert(res.redirect.calledWith(expectedUrl));
});
});
describe('can not save code', function() {
var expectedUrl = 'back';
beforeEach(function() {
promise.rejects();
});
it('redirects to the expected URL', function(){
ch.oAuthCallback(req, res);
assert(res.redirect.calledWith(expectedUrl));
})
})
});
Это работает отлично.
Что ж, проще всего было бы вообще не заглушить его синхронный запуск, поскольку это может изменить порядок выполнения и использовать встроенную поддержку обещаний Mocha (или jasmine-as-обещано, если используется jasmine).
Причина в том, что могут быть такие случаи, как:
somePromise.then(function(){
doB();
});
doA();
Если вы вызываете обещания синхронно разрешать порядок выполнения - и, следовательно, вывод программы изменяется, делая тест бесполезным.
Напротив, вы можете использовать тестовый синтаксис:
describe("the test", () => { // use arrow functions, node has them and they're short
it("does something", () => {
return methodThatReturnsPromise().then(x => {
// assert things about x, throws will be rejections here
// which will cause a test failure, so can use `assert`
});
});
});
Вы можете использовать даже более легкий синтаксис стрелки для отдельных строк, что делает тест еще менее подробным:
describe("the test", () => { // use arrow functions, node has them and they're short
it("does something", () =>
methodThatReturnsPromise().then(x => {
// assert things about x, throws will be rejections here
// which will cause a test failure, so can use `assert`
});
);
});
В RSVP вы не можете установить планировщик, насколько мне известно, так что в любом случае абсолютно невозможно синхронно тестировать вещи, другие библиотеки, такие как bluebird, позволяют вам делать это на свой страх и риск, но даже в библиотеках, которые позволяют вам это делать, вероятно, это не так. лучшая идея