Как условно сбросить $timeout в модульном тесте Angular
У меня есть код, который выглядит примерно так:
function doThing() {
if (invalidInput) {
console.error('Invalid input.');
return;
}
$timeout(function() {
MyService.doThing();
}, 1000);
}
Я хочу проверить это MyService.doThing
не вызывается при вводе неверного ввода.
Если я позвоню doThing(invalidInput)
без выполнения $timeout.flush()
, MyService.doThing
не будет вызван, независимо от того, есть ли у меня строки 2-5. Таким образом, чтобы действительно проверить, MyService.doThing
вызывается, когда вводится неверный ввод, мне нужно позвонить $timeout.flush
,
Проблема в том, что выдает ошибку, если я пытаюсь сбросить, когда нечего сбросить. Error: No deferred tasks to be flushed
,
Как я могу справиться с этим сценарием? Я хотел бы сделать что-то вроде, $timeout.flushIfFlushable()
,
2 ответа
Никакая неопределенность не приветствуется в модульных тестах, должно быть предсказуемо, есть ли что-то для $timeout
или нет.
В этом фрагменте кода должны быть проверены два случая, в обоих $timeout
не должен быть черным ящиком, вместо этого он должен быть заглушкой (по желанию, шпионом, который оборачивается вокруг реальной службы).
beforeEach(module('app', ($provide) => {
$provide.decorator('$timeout', ($delegate) => {
var timeoutSpy = jasmine.createSpy().and.returnValue($delegate);
// methods aren't copied automatically to spy
return angular.extend(timeoutSpy, $delegate);
});
}));
Первый фальси invalidInput
:
...
MyService.doThing();
expect($timeout).not.toHaveBeenCalled();
А второй правдивый invalidInput
:
...
MyService.doThing();
expect($timeout).toHaveBeenCalledWith(jasmine.any(Function), 1000);
$timeout.flush();
expect(MyService.doThing).toHaveBeenCalledTimes(2);
Независимо от этого случая, как правило, полезно возвращать обещания из функций, основанных на обещаниях:
function doThing() {
if (invalidInput) {
console.error('Invalid input.');
return;
}
return $timeout(function() {
return MyService.doThing();
}, 1000);
}
Таким образом, вызывающие (наиболее вероятные спецификации) могут иметь некоторый контроль над асинхронным поведением функции.
Отвечая на вопрос напрямую, ожидаемый способ сделать flushIfFlushable()
try {
$timeout.verifyNoPendingTasks(); // just for semantics, not really necessary
$timeout.flush();
} catch (e) {}
Чего следует избегать по причинам, перечисленным выше.
Я предлагаю определить два отдельных модульных теста, чтобы проверить поведение вашего doThing
функция. Попробуйте следующий код:
- контроллер
(function () {
'use strict';
angular.module('myApp', [])
.controller('MainCtrl', function ($timeout, MyService) {
var vm = this;
vm.invalidInput = true;
vm.doThing = doThing;
function doThing() {
if (vm.invalidInput) {
return;
}
$timeout(function () {
MyService.doThing();
}, 1000);
}
});
})();
- обслуживание
(function () {
'use strict';
angular.module('myApp').service('MyService', MyService);
function MyService() {
this.doThing = function () {
// doThing code
};
}
})();
- Модульный тест
'use strict';
describe('Controller: MainCtrl', function () {
beforeEach(module('myApp'));
var vm,
$timeout,
MyService;
beforeEach(inject(function (_$controller_, _$timeout_, _MyService_) {
$timeout = _$timeout_;
MyService = _MyService_;
vm = _$controller_('MainCtrl', {
$timeout: $timeout,
MyService: MyService
});
}));
it('should call doThing for valid inputs', function () {
spyOn(MyService, 'doThing').andCallThrough();
vm.invalidInput = false;
vm.doThing();
$timeout.flush();
expect(MyService.doThing).toHaveBeenCalled();
});
it('should not call doThing for invalid inputs', function () {
spyOn(MyService, 'doThing').andCallThrough();
vm.invalidInput = true;
vm.doThing();
expect(MyService.doThing).not.toHaveBeenCalled();
});
});
С первым тестом мы ожидаем позвонить MyService.doThing()
функция. С другой стороны, если у вас есть invalidInput
как true
предыдущая функция не должна вызываться.
Я надеюсь, что это помогает.