fishbone
fishbone

Reputation: 3259

How to detect missing tick() in fakeAsync()

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.

Upvotes: 1

Views: 1021

Answers (2)

Coderer
Coderer

Reputation: 27304

If your tests are at least written the same way, one or the other should fail if a tick is missing / wrong. It isn't perfect, but you could try moving your test logic into a common function so that you have some degree of confidence that it does what you meant:


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();
}));

Now, your second test will fail (because expect ran before the await resolved). By debugging this, you'll either notice the logic error in your code under test, fix that, then find the test error. Or, you'll find and fix the test error, which will cause the first test to fail, and then find the logic error. The only way you can miss it is if you tick wrong in one test but not the other (or fail to test all conditional branches).

Upvotes: 1

fishbone
fishbone

Reputation: 3259

This issue reveals that currently fakeAsync()doesn't throw an exception for pending micro tasks but only for pending timers. At the moment there seems to be no way to ensure there are no pending micro tasks at the end of the test. However, the developers are going to check if this is a feature.

Upvotes: 0

Related Questions