I wrote this test for my Angular app:
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();
}));
This is the component method I'm testing:
async deleteEntry(person: Person) {
if (await this.appService.confirm().toPromise()) {
return;
}
try {
await this.personService.delete(person).toPromise();
} catch(resp) {
this.appService.errorMsgBox();
}
}
(confirm()
's purpose is to show a confirmation dialog and return an Observable emitting true
/ false
depending on the user input)
If you look carefully, there is an error in my component function. I forget the !
-operator when checking the result of confirm()
. The correct code would be
if (!await this.appService.confirm().toPromise()) {
However, the test will pass. I'm not 100% sure, but I guess it passes, because the expect()
-statement at the end performs its check before confirm()
has returned its value. So yes, of course, personService.delete()
has not been called. If I uncomment the tick()
the test works as expected, and detects the error.
Now I expected fakeAsync()
to throw an error due to pending microtasks. To my suprise, it does not. The test passes without any errors or warnings, although the docs say:
If there are any pending timers at the end of the function, an exception will be thrown.
So it seems we have a race condition here, i.e. confirm()
is resolved before returning fakeAsync()
but after expect()
. If this is possible, what is the deal about fakeAsync()
, if not controlling those things?
Probably I and other developers will forget tick()
or flushMicrotasks()
in future as well. So I wonder, how to avoid this. Is there some kind of helper function I'm missing that I can put in afterEach()
? Or is the behavior of fakeAsync()
an Angular bug, i.e. it should throw an exception?
EDIT
See a full working example of my problem on Stackblitz: https://stackblitz.com/edit/angular-cnmubr. Notice that you have to click the 'refresh'-button of the inner browser view (next to the editor) if you want to re-run the test or after you changed something. The auto-reload feature will not work and throw errors.
I submitted an issue, like someone suggested in the comments.