Я написал этот тест для своего приложения Angular:
it('should request confirmation before deleting & abort action if user declined', fakeAsync(() => {
spyOn(appService, 'confirm').and.returnValue(of(false));
spyOn(personService, 'delete').and.callThrough();
component.deleteEntry(testPerson);
//tick(); // Missing tick()!
expect(personService.delete).not.toHaveBeenCalled();
}));
Это компонентный метод, который я тестирую:
async deleteEntry(person: Person) {
if (await this.appService.confirm().toPromise()) {
return;
}
try {
await this.personService.delete(person).toPromise();
} catch(resp) {
this.appService.errorMsgBox();
}
}
(Цель confirm() - показать диалоговое окно подтверждения и вернуть Observable, излучающий true / false, в зависимости от ввода пользователя)
Если вы посмотрите внимательно, в моей функции компонента есть ошибка. Я забываю !-оператор при проверке результата confirm(). Правильный код будет
if (!await this.appService.confirm().toPromise()) {
Однако испытание пройдет. Я не уверен на 100%, но я полагаю, что он проходит, потому что оператор expect() в конце выполняет свою проверку до того, как confirm() вернет свое значение. Так что да, конечно, personService.delete() не назывался. Если я раскомментирую tick(), тест будет работать, как ожидалось, и обнаружит ошибку.
Теперь я ожидал, что fakeAsync() выдаст ошибку из-за незавершенных микрозадач. К моему удивлению, это не так. Тест проходит без ошибок и предупреждений, хотя документы говорят:
If there are any pending timers at the end of the function, an exception will be thrown.
Таким образом, похоже, что у нас есть состояние гонки, то есть confirm() разрешается до возврата fakeAsync(), но после expect(). Если это возможно, в чем дело с fakeAsync(), если не контролировать эти вещи?
Наверное, я и другие разработчики забудем tick() или flushMicrotasks() в будущем. Поэтому мне интересно, как этого избежать. Есть ли какая-то вспомогательная функция, которую мне не хватает, которую я могу добавить в afterEach()? Или поведение fakeAsync() является ошибкой Angular, т.е. должно вызывать исключение?
РЕДАКТИРОВАТЬ
См. Полный рабочий пример моей проблемы на Stackblitz: https://stackblitz.com/edit/angular-cnmubr. Обратите внимание, что вам нужно нажать кнопку «Обновить» во внутреннем представлении браузера (рядом с редактором), если вы хотите повторно запустить тест или после того, как вы что-то изменили. Функция автоматической перезагрузки не будет работать и выдает ошибки.
Я отправил проблема, как кто-то предложил в комментариях.
confirm() возвращает наблюдаемый объект, который излучает true, если пользователь подтверждает действие, и false в противном случае. toPromise() преобразует Observable в Promise, т.е. подписывается и разрешает возвращенное обещание с первым значением, полученным от Observable. of(false) имитирует отказ пользователя от действия (нажатие кнопки «нет» в диалоговом окне подтверждения)
Ой ну спасибо. Теперь это имеет для меня смысл. Я предполагаю, что вы можете предпочесть оболочку asyncfakeAsync и записать свои ожидания как microtask. tick вызывает ожидание выполнения обещания confirm.
@fishbone, не могли бы вы опубликовать репо с воспроизведением? Или разместите здесь вопрос, github.com/angular/zone.js, я проверю, спасибо.





Эта проблема показывает, что в настоящее время fakeAsync() не генерирует исключение для ожидающих микрозадач, а только для ожидающих таймеров. На данный момент, похоже, нет способа гарантировать отсутствие незавершенных микрозадач в конце теста. Однако разработчики собираются проверить, является ли это функцией.
Если ваши тесты, по крайней мере, написаны одинаково, то один или другой должен завершиться неудачно, если tick отсутствует / неправильный. Это не идеально, но вы можете попробовать перенести свою тестовую логику в общую функцию, чтобы иметь некоторую степень уверенности в том, что она делает то, что вы имели в виду:
function callDelete() {
spyOn(personService, 'delete').and.callThrough();
component.deleteEntry(testPerson);
//tick(); // Missing tick()!
}
it('aborts delete if user declined', fakeAsync(() => {
spyOn(appService, 'confirm').and.returnValue(of(false));
callDelete();
expect(personService.delete).not.toHaveBeenCalled();
}));
it('deletes if user accepted', fakeAsync(() => {
spyOn(appService, 'confirm').and.returnValue(of(true));
callDelete();
expect(personService.delete).toHaveBeenCalled();
}));
Теперь ваш второй тест не удастся (потому что expect был запущен до того, как был разрешен await). Отлаживая это, вы либо заметите логическую ошибку в тестируемом коде, либо исправите ее, а затем найдете ошибку теста. Или вы найдете и исправите ошибку теста, которая приведет к сбою первого теста, а затем обнаружите логическую ошибку. Единственный способ пропустить это, если вы ошиблись с tick в одном тесте, но не в другом (или не смогли протестировать все условные переходы).
this.appService.confirm().toPromise()возвращает наблюдаемое или значение? Я вижу, что шпион издевается над своим returnValue как наблюдаемым т.е.of(false). AFAIK, это должно быть правдой