Angular 2 Testing - Async function call - when to use

36,104

Solution 1

async will not allow the next test to start until the async finishes all its tasks. What async does is wrap the callback in a Zone, where all asynchronous tasks (e.g. setTimeout) are tracked. Once all the asynchronous tasks are complete, then the async completes.

If you have ever worked with Jasmine outside out Angular, you may have seen done being passed to the callback

it('..', function(done) {
  someAsyncAction().then(() => {
    expect(something).toBe(something);
    done();
  });
});

Here, this is native Jasmine, where we tell Jasmine that this test should delay completion until we call done(). If we didn't call done() and instead did this:

it('..', function() {
  someAsyncAction().then(() => {
    expect(something).toBe(something);
  });
});

The test would complete even before the expectation, because the promise resolves after the test is finished executing the synchronous tasks.

With Angular (in a Jasmine environment), Angular will actually call done behind the scenes when we use async. It will keep track of all the asynchronous tasks in the Zone, and when they are all finished, done will be called behind the scenes.

In your particular case with the TestBed configuration, you would use this generally when you want to compileComponents. I rarely run into a situation in which I would have to call it otherwise

beforeEach(async(() => {
   TestBed.configureTestingModule({
     declarations: [MyModule],
     schemas: [NO_ERRORS_SCHEMA],
   })
   .compileComponent().then(() => {
      fixture = TestBed.createComponent(TestComponent);
   });
}));

When testing a component that uses templateUrl (if you are not using webpack), then Angular needs to make an XHR request to get the template, so the compilation of the component would be asynchronous. So we should wait until it resolves before continuing testing.

Solution 2

When you make an async call in your test the actual test function is completed before the async call is completed. When you need to verify some state when the call was completed (which is usually the case) then the test framework would report the test as completed while there is still async work going on.

With using async(...) you tell the test framework to wait until the return promise or observable is completed before treating the test as completed.

it('should show quote after getQuote promise (async)', async(() => {
  fixture.detectChanges();

  fixture.whenStable().then(() => { // wait for async getQuote
    fixture.detectChanges();        // update view with quote
    expect(el.textContent).toBe(testQuote);
  });
}));

The code passed to then(...) will be executed after the test function itself completed. With async() you make the test framework aware, that it needs to wait for promises and observables to complete before treating the test as completed.

See also

Share:
36,104
xiotee
Author by

xiotee

Updated on February 27, 2020

Comments

  • xiotee
    xiotee about 4 years

    When do you use the async function in the TestBed when testing in Angular 2?

    When do you use this?

     beforeEach(() => {
            TestBed.configureTestingModule({
                declarations: [MyModule],
                schemas: [NO_ERRORS_SCHEMA],
            });
        });
    

    And when do you use this?

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [MyModule],
            schemas: [NO_ERRORS_SCHEMA],
        });
    }));
    

    Can anyone enlighten me on this ?

  • vince
    vince almost 7 years
    Great answer @peeskillet. Just to make sure I understand it: When you have an inline template, async is not necessary. When you are using templateUrl, it is. However, including async will not "break" an inline-template component. Do you think it's safe to say that one can just default to using async for every test?
  • Paul Samsotha
    Paul Samsotha almost 7 years
    @vincecampanale The templateUrl only matters during configuration in the beforeEach. In which case you need to call compileComponents. It has nothing to do with using async on each test if that's what you're asking. As far as being safe (when you should call compileComponents), see When am I supposed to call compileComponents
  • vince
    vince almost 7 years
    Got it. Another question: the Angular CLI generates tests that call fixture.detectChanges() in a beforeEach(). Is it best practice to always invoke this function before running every test? If you believe this is unrelated, I'd be happy to make a separate question on SO for it.
  • Paul Samsotha
    Paul Samsotha almost 7 years
    @vincecampanale It's not always the case that you want it called before the test. Sometimes you might want to call it after you do some initialization. You need to understand what calling it actually does. Most of the time it should be OK though. But I personally don't like that they took it upon themselves to make that decision. But I see a lot of people run into the problem where they forget to call it, and they wonder why somethings not working. So maybe it's better that they do generate the call. The location may be debatable, but at least they call it
  • vince
    vince almost 7 years
    Awesome. Thank you for confirming that. No amount of Google searching would return a straightforward answer like that one. Gotta love SO. So, in summary, fixture.detectChanges() should be used when it's appropriate and it is NOT safe to assume that it should just be called before every test. Rather, it is something to be aware of and use appropriately.
  • Paul Samsotha
    Paul Samsotha almost 7 years
    @vincecampanale Generally when you want the view (re)rendered is when you should call it. For instance Create Component -> render view. But if you want to initialize something first like Create Component -> change value in component that is used to render -> render view. That's what I mean by maybe you want to initialize something first
  • Paul Samsotha
    Paul Samsotha almost 7 years
    Oh and one more thing. The first time you call it, is when ngOnInit in the component is called. Sometimes this matters when testing
  • vince
    vince almost 7 years
  • PhiLho
    PhiLho over 2 years
    It became angular.io/api/core/testing/waitForAsync, probably because async is an ambiguous name now (Promises).