1
votes

Firstly, I don't believe it is a duplicate as the SO questions and answers describe testing of promise handling in angular1 and are resolved by calling $timeout.flush() or $rootScope.$apply() so no applicable to this Angular2 case.

My question is how I can test logic of Angular2 component which is being executed after resolving a promise. (Preferably in Jasmine).

To illustrated it I modified quick start examples from Angular2 docs. Simple component lists existing heros and allows registration of a new one. The heros list and registration is done by a service, which returns responses as Promises (with list of heros, or newly registered hero object).

Now once a new hero is registered (and Promise resolved) I want the component to make the hero selected and retrieve again the hero list:

...
register(heroName:string) {
  this.postRegistration(this._heroService.registerHero(heroName));
}
//this extra method helps testing without stubing of the service
postRegistration(heroPromise:Promise<Hero>) {
  heroPromise.then( hero => {
      //I want to test that the two actions below took place
      this.selectedHero = hero;
      this.getHeroes(); })
}

getHeroes() {
this._heroService.getHeroes().then(heroes => this.heroes = heroes);
}

I split registration in two methods, so it can be tested without stubing of the service

Using Jasmine asynchronous testing with done() I can easily test the service behaviour, ie. that it returns correct promises. But how to test, that the component invoked getHeros to refresh its list or correctly set the selected hero.

I managed to test for setting the variable with the spec:

it("postRegistration sets selectedHero as registered one",done =>{
    let promise = service.registerHero("Bosek"); //Promise.resolve(hero);
    component.postRegistration(promise);
    promise.then( hero => {
        expect(component.selectedHero).toBe(hero);
        expect(component.heroes.indexOf(hero)).toBeGreaterThan(0);
        done();
    })
}) 

I "hooked" my assertion to the same promise which was passed to the component.

My first question: Was I lucky or is it guarantee that if I subscribe to the same promise and call from its 'then' clause jasmine done() it will wait till my other "subscribers" (in that case component) process that promise and subsequent ones.

Secondly, Is there a way to force all Promises to be synchronous during testing, that would solved the whole problem.

I found mock-promise project https://github.com/charleshansen/mock-promises which allows "firing" the promises but it has been dead for 2 years.

This problem should be common enough to have a standard answer.

I am asking in the context of Angular2 and Jasmine, but, I am interest in simple 'unit test' approach as well, for example the current test is fully independent of Angular framework. And typescript please as I don't work with JS normally.

The whole code (it is a simple clone of Angular2 Hero quick start example) is under: https://github.com/tzielins/angular-start-project

2

2 Answers

2
votes

@Gunter answer put me on the right track, but as he does not explicit address the problem I am writing my own answer.

My first question

I was indeed only lucky. Making subsequent calls to the same promise, have no consequences on the previous ones. And although this test passed, other with similar pattern fails.

Returning promises from the "bussiness" method and chaining to it, is not always an option as it depends on the method logic, so Gunter suggestion not always can be followed.

Secondly

Gunter mentioned Angular2 fakeAsync and it is the correct way of testing for side effects of Promise processing. Although it is a part of angular2 test library, it can be used in 'a unit test' way as it does not requier injections or other angular dependencies.

The use it, call flushMicrotasks inside fakeAsync body and test for side effects:

import {it,fakeAsync,tick,flushMicrotasks} from 'angular2/testing';
...
    it('correct side effect after processing promise',<any>fakeAsync(()  => {

        let p = Promise.resolve(X);

        //invoking business logic
        component.dealWithIt(p);

        flushMicrotasks();

        //now the side effects can be tested in synchronous way
        expect(component.selectedHero).toBe(hero);
        ...
    }));

tick() has similar effect on promises.

Even the new promises created inside business method are guaranteed to be completed. I asked about it here: Does fakeAsync guarantee promise completion after tick/flushMicroservice and I also found Angular test specs that confirm it.

1
votes

My first question:

You should always ensure to return promises

register(heroName:string) {
  return this.postRegistration(this._heroService.registerHero(heroName));
}
//this extra method helps testing without stubing of the service
postRegistration(heroPromise:Promise<Hero>) {
  return heroPromise.then( hero => {
      //I want to test that the two actions below took place
      this.selectedHero = hero;
      return this.getHeroes(); })
}

getHeroes() {
  return this._heroService.getHeroes().then(heroes => this.heroes = heroes);
}

this way async parts are chained when you call .then(...) on the return value, otherwise the async calls become their own disconnected chain of events and you don't get any information about when they are executed or when they are completed.

Secondly

There is a fakeAsync which should allow that for testing Angular2 testing with fakeAsync (I haven't tried it myself)

Otherwise promises are async and nothing can change that.