Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 async unit testing with jasmine

I'm writing an angular2 app and having trouble understanding how to write tests for async code using jasmine. For whatever reason, I'm not seeing a lot of examples that seem terribly applicable to my situation.

I'm currently trying to test a service (not a component) that has a async dependency on another service. I'm not 100% sure at what point in the test it's valid to check the results of the async call. Can you just call expect() inside the async handler for your service?

service.foo()
    .then((data) => {
        //do I check the results in here?
        expect(data).toEqual({ a: 1, b: 2 });
        expect(mockDep.get).toHaveBeenCalled();
    });

Here is the full test.

import { TestBed, inject } from '@angular/core/testing';
import { MyService } from './my.service.ts';
import { MyDependency } from './dependency.service.ts';

class MockDependency {
    doSomething(): Promise<any> {
        throw Error('not implemented');
    };
}

describe('some tests', () => {
    beforeEach(() => {
        TestBed.configureTestingModule({
            providers: [
                MyService,
                {
                    provide: MyDependency, useClass: MockDependency
                }
            ]
        });
    });
});

it('should do something', inject([MyService, MyDependency], (service: MyService, mockDep: MyDependency) => {
    spyOn(mockDep, 'doSomething').and.callFake(function () {
        return Promise.resolve({ a: 1, b: 2 });
    });

    service.foo()
        .then((data) => {
            //do I check the results in here?
            expect(data).toEqual({ a: 1, b: 2 });
            expect(mockDep.get).toHaveBeenCalled();
        });
}));
like image 872
d512 Avatar asked Mar 20 '17 18:03

d512


People also ask

How do you test async in Jasmine?

If an operation is asynchronous just because it relies on setTimeout or other time-based behavior, a good way to test it is to use Jasmine's mock clock to make it run synchronously. This type of test can be easier to write and will run faster than an asynchronous test that actually waits for time to pass.

What is the difference between async ()' and fakeAsync ()'?

tl;dr. In almost all cases, they can be used interchangeably, but using fakeAsync()/tick() combo is preferred unless you need to make an XHR call, in which case you MUST use async()/whenStable() combo, as fakeAsync() does not support XHR calls. For the most part they can be used interchangeably.

What is fakeAsync?

fakeAsynclink Wraps a function to be executed in the fakeAsync zone: Microtasks are manually executed by calling flushMicrotasks() . Timers are synchronous; tick() simulates the asynchronous passage of time.


1 Answers

There's two aspects of dealing with async tests that you have to concern yourself about if you want to ensure your tests are actually reliable.

First, you have to ensure that if a result is retrieved asynchronously, that you wait until the result is available before you try to test for it.

Hence, if the asynchronous result is a promise, for eg, you can put your expect in your then handler, just as you indicated in your question.

The second issue that you have to concern yourself with is forcing your test itself to wait for your expectations to execute before giving a positive (or negative) result. If you don't deal with this, you can have cases where your expectation fails, but because your test did not wait for your asynchronous action to complete before finishing, the test reports a false positive.

There are several ways of 'making your test wait'.

The pure jasmine way is to pass a done handler into your it function. Then jasmine will wait until that done handler is called before considering the test complete.

eg.

it('tests an async action', (done) => {
   asyncAction().then(result => {
     expect(result).toEqual(true);
     done();
   });
});

However, angular's testing framework adds two other options to this. The first is easier to grasp if you are comfortable with async programming.

it('tests an async action', async(() => {
   asyncAction().then(result => {
     expect(result).toEqual(true);
   });
}));

in this case, you basically wrap your test handler in an async function. This function will force the test to wait for any async results (eg promises, observables etc) to return a result, before allowing the test to complete.

The second method is to use fakeAsync, which allows you to hide the async nature of your test entirely.

it('tests an async action', fakeAsync(() => {
   let myResult;
   asyncAction().then(result => {
     myResult = result;
   });

   tick();   <--- force all async actions to complete
   expect(myResult).toEqual(true);
}));

fakeAsync hooks into all async functions and allows you to treat them as synchronous. You can use the tick() function to 'force your test to wait' for async tasks to complete before continuing.
(It's not quite doing that, but conceptually, you can think of it that way).

See the Angular docs to learn more

like image 147
snorkpete Avatar answered Sep 21 '22 17:09

snorkpete