1

I tried write tests for my angular component which is using service. I initialized my userServiceStub property isLoggedIn with true, but when I run tests components UserService property is false. I tried remove Injectable() decorator and change anonymous object to UserService.

Tests

import { async, ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { WelcomeComponent } from './welcome.component';
import { UserService, User } from './model';

class UserServiceMock{
    isLoggedIn: boolean = true;
    user: User = {name: 'Mock user'};
}

describe('Welcome component tests', () =>{

    let component:      WelcomeComponent;
    let fixture:        ComponentFixture<WelcomeComponent>;
    let debugElment:    DebugElement;
    let htmlElement:    HTMLElement;
    let userService:    UserService;

    let userServiceStub: {
        isLoggedIn: boolean;
        user: { name: string }
    };

    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [WelcomeComponent],
        })
        .overrideComponent(WelcomeComponent, {
            set: {
                providers: [
                    {provide: UserService, useClass: UserServiceMock}
                ]
            }
        })
        .compileComponents()
        .then(() =>{
            fixture = TestBed.createComponent(WelcomeComponent);

            component = fixture.componentInstance;

            userService = TestBed.get(UserService);
            console.log(userService);
            debugElment = fixture.debugElement.query(By.css('.welcome'));
            htmlElement = debugElment.nativeElement;
        });
    });

    it('stub object and injected UserService should not be the same', () =>{
        expect(userServiceStub === userService).toBe(false);
    });

    it('changing the stub object has no effect on the injected service', () =>{
        userServiceStub.isLoggedIn = false;
        expect(userService.isLoggedIn).toBe(true);
    });

    it('should welcome user', () => {
        fixture.detectChanges();
        const content = htmlElement.textContent;
        expect(content).toContain('Welcome');
    })
})

Welcome Component

import { Component, OnInit } from '@angular/core';
import { UserService }       from './model';

@Component({
  selector: 'app-welcome',
  template: '<h3 class="welcome" ><i>{{welcome}}</i></h3>',
  providers: [UserService]
})
export class WelcomeComponent  implements OnInit {
  welcome: string = '-- not initialized yet --';
  constructor(private userService: UserService) { }

  ngOnInit(): void {
    this.welcome = this.userService.isLoggedIn ?
      'Welcome, ' + this.userService.user.name :
      'Please log in.';
  }
}

User Service

import { Injectable } from '@angular/core';

@Injectable()
export class UserService {
    isLoggedIn: boolean;
    user: User;

}

export class User{
    name: string;
}

Failed test result link

My question is: How to correctly inject service?

  • have you try to put `providers: [UserService]` inside @Component({}) in WelcomeComponent ? You can also take a look at this http://stackoverflow.com/questions/39894179/angular-2-test-spec-for-a-component-that-provides-a-service?rq=1 – Elmer Dantas Dec 12 '16 at 11:14
  • I added this line ` providers: [UserService] ` to WelcomeComponent and used TestBed.overrideComponent with my UserService. All tests started to fail, because userService was undefined. – OneGuyFromEU Dec 13 '16 at 17:52
  • Try to add async to beforeEach. Then it will wait until your variables are initialized – yurzui Dec 13 '16 at 20:17

1 Answers1

1

1) You have to use async or fakeAsync if you use compileComponent():

2) As you're providing service within component you should use:

fixture.debugElement.injector.get(UserService);

to get injected service

3) You cannot change property on undefined object:

let userServiceStub: {
    isLoggedIn: boolean;
    user: { name: string }
};

userServiceStub.isLoggedIn = false;

userServiceStub is undefined. And I don't understand why userServicesSub is here if you don't use it as useValue as described here https://angular.io/docs/ts/latest/guide/testing.html#!#final-setup-and-tests

So your test might look like:

describe('Welcome component tests', () =>{
    let component:      WelcomeComponent;
    let fixture:        ComponentFixture<WelcomeComponent>;
    let debugElment:    DebugElement;
    let htmlElement:    HTMLElement;
    let userService:    UserService;

    let userServiceStub: {
        isLoggedIn: boolean;
        user: { name: string }
    } = { 
      isLoggedIn: true, 
      user: { name: 'Stub user'}
    };

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [WelcomeComponent],
        })
        .overrideComponent(WelcomeComponent, {
            set: {
                providers: [
                    {provide: UserService, useClass: UserServiceMock}
                ]
            }
        })
        .compileComponents()
        .then(() =>{
            fixture = TestBed.createComponent(WelcomeComponent);

            component = fixture.componentInstance;

            //userService = TestBed.get(UserService);

            userService = fixture.debugElement.injector.get(UserService);

            console.log(userService);
            debugElment = fixture.debugElement.query(By.css('.welcome'));
            htmlElement = debugElment.nativeElement;
        });
    }));

    it('stub object and injected UserService should not be the same', () =>{
        expect(userServiceStub === userService).toBe(false);
    });

    it('changing the stub object has no effect on the injected service', () =>{
        userServiceStub.isLoggedIn = false;
        expect(userService.isLoggedIn).toBe(true);
    });

    it('should welcome user', () => {
        fixture.detectChanges();
        const content = htmlElement.textContent;
        expect(content).toContain('Welcome');
    })
})

Live Example

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • Thank you! In first version I used userServicesSub, but then I tried to create mock and forget to remove this stub. – OneGuyFromEU Dec 14 '16 at 10:27