3

I have the following component which retrieves data from an Angular service:

export class MyComponent {
    constructor() {
        myService.get().then(() => {
            console.log('hello from constructor');
        });
    }
}

And then my unit test:

///////////

it('does something', () => {
    console.log('hello from unit test');
});

///////////

Unfortunately this results in the following log:

> hello from unit test
> hello from constructor

How can I make sure that the constructor finishes before running the unit test?

Igor
  • 60,821
  • 10
  • 100
  • 175
Jelle den Burger
  • 1,428
  • 17
  • 31
  • Your question isn't really about a constructor not being called before your tests, but about you expecting your service call to be made before the tests. Might want to isolate that (i.e. removing the constructor out of the picture) so your question is clearer. (IMO) – Wagner Danda da Silva Filho Oct 24 '17 at 15:30
  • 1
    It isn't `hello from constructor`. It is `hello from asynchronous code that runs in constructor`. Generally a thing like that is an antipattern. – Estus Flask Oct 24 '17 at 15:46

2 Answers2

4

Do not use the constructor to load data, implement the OnInit interface instead.

import { OnInit } from '@angular/core';
export class MyComponent implements OnInit {

    constructor(private myService: MyService) {}

    ngOnInit() {
        myService.get().then(() => {
            console.log('hello from constructor');
        });
    }
}
  • See also the angular documentation Lifecycle Hooks.
  • Do not forget to inject your dependencies like your myService instance, I added it to the constructor.

Testing

I recommend you read over the Testing documentation. It is a lot of information but it is worth it. Here is code that you would use to unit test your component.

let comp: MyComponent ;
let fixture: ComponentFixture<MyComponent>;

beforeEach(async(() => {
    TestBed.configureTestingModule({
        declarations: [MyComponent],
            providers: [
                { provide: MyService, useValue: {} }
            ]
        })
        .compileComponents(); 

    TestBed.compileComponents();
    fixture = TestBed.createComponent(MyComponent);
    comp = fixture.componentInstance;
}));


it('initializes the component', fakeAsync(() => {
    var service = TestBed.get(MyService); // get your service
    service.get = () => {
            return Promise.resolve(); // you can pass data here if the service returns something
        };

    // here you could add an expect to validate component state before the call or service completes

    comp.ngOnInit(); // call ngOnInit
    tick(); // simulate the promise being resolved

    expect(service.get.toHaveBeenCalled);
    // here you could add an expect to validate component state after the service completes
}));
Igor
  • 60,821
  • 10
  • 100
  • 175
  • This works, thanks. But how does Angular know that the ngOnInit finished its async code before continuing and running my unit test? – Jelle den Burger Oct 25 '17 at 07:01
  • With this code you are faking the service call and you are faking the asynchronousness. Look at beforeEach(async(() and it('...', fakeAsync(() and service.get = () => Promise.resolve() and the tick() calls. All of those parts play their roles to help you fake timing for the component to have the experience as if it has called a service and that answered in a delayed manner. Anyway, tick() simulates the time ticking by. Removing it would break the test. – Janos Vinceller Jan 22 '22 at 08:24
0

Your constructor is executing before your tests, however, your constructor's code makes an async call to a service, and that is executed after your tests.

First, you should really consider moving that service call away from the constructor.

Second, when writing tests for a component you usually spy on service calls and check that they were called, you don't actually make the call, you mock it up. Look up the documentation for 'spyOn'.

Lastly, if you want something to happen before your tests, take a look at 'beforeEach'. Anyways, hope this helps.