35

Angular 4 unit test for a subscribe.

I want to test that my subscribe returns an array of Users. I want to mock a list of users and test a function called getUsers.

The subscribe unit test doesnt work. Something wrong with the syntax.

This is my Users interface:

export interface User {
  id: number;
  name: string;
  username: string;
  email: string;
  address: {
    street: string;
    suite: string;
    city: string;
    zipcode: string;
    geo: {
      lat: string;
      lng: string;
    }
  };
  phone: string;
  website: string;
  company: {
    name: string;
    catchPhrase: string;
    bs: string;
  };
};

This is my component I want to test:

import { Component, OnInit } from "@angular/core";
import { Observable } from "rxjs/Observable";

import { UserService } from "../../services/user.service";
import { User } from "../../models/user.model";

@Component({
  selector: "home-users",
  templateUrl: "./home.component.html"
})

export class HomeComponent implements OnInit {
  private listOfUsers: User[];

  constructor(private userService: UserService) {
  }

  ngOnInit() {
    this.getUsers();
  }

  getUsers(): void {
    this.userService.getUsers().subscribe(users => {
      this.listOfUsers = users;
    });
  }
}

This is my unit test attempt:

import { TestBed, async, inject } from "@angular/core/testing";
import { HttpModule } from "@angular/http";

import { HomeComponent } from "./home.component";
import { UserService } from "../../services/user.service";
import { User } from "../../models/user.model";

describe("HomeComponent", () => {
  let userService;
  let homeComponent;
  let fixture;
  let element;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        HomeComponent
      ],
      providers: [
        UserService
      ],
      imports: [HttpModule]
    }).compileComponents();
  }));

  beforeEach(inject([UserService], s => {
    userService = s;
    fixture = TestBed.createComponent(HomeComponent);
    homeComponent = fixture.componentInstance;
    element = fixture.nativeElement;
  }));

  it("should call getUsers and return list of users", async(() => {
    // Arrange
    let response: User[] = [];

    // Act
    homeComponent.getUsers();

    fixture.detectChanges();
    fixture.whenStable().subscribe(() => {
        expect(homeComponent.listOfUsers).toEqual(response);
    });
  }));
});
Sharikov Vladislav
  • 7,049
  • 9
  • 50
  • 87
AngularM
  • 15,982
  • 28
  • 94
  • 169
  • 1
    @Carsten the test doesnt work. Seems to be something wrong with the syntax for the subscribe part of the test – AngularM Aug 23 '17 at 12:40

3 Answers3

66

You need this for version rxjs@6 and above. For older rxjs version answer is below:

    import { of } from 'rxjs';

    it("should call getUsers and return list of users", async(() => {
      const response: User[] = [];

      spyOn(userService, 'getUsers').and.returnValue(of(response))

      homeComponent.getUsers();

      fixture.detectChanges();
    
      expect(homeComponent.listOfUsers).toEqual(response);
    }));

For old rxjs version change import from:

    import { of } from 'rxjs';

to

    import { of } from 'rxjs/observable/of';
BadPiggie
  • 5,471
  • 1
  • 14
  • 28
Sharikov Vladislav
  • 7,049
  • 9
  • 50
  • 87
  • Actually I can not try now :D Did this work? I added jest support and yesterday everything was working. But now I get some strange errors :) – Sharikov Vladislav Aug 23 '17 at 12:57
  • This worked! Do you think its a worthwhile test? Or should I only test my service rather than a component? – AngularM Aug 23 '17 at 12:58
  • I think you need test if you have some logic. For example you have some data received from server and you have server processing this data. This is critical logic and must not be broken. You have to unit test this. In your example: well why not if it is easy to test. This will defend future developers from accidently removing this subscription. – Sharikov Vladislav Aug 23 '17 at 13:01
  • Anyone has an error at of(response) in the spyOn line? mine is User[] not assignable to void.. how to resolve it since there is no return value supposedly? – iBlehhz Jan 07 '20 at 04:50
  • what is the return type of getUsers method in User interface / class? – Sharikov Vladislav Jan 14 '20 at 10:09
  • Are you sure you spy on the service? Component has the same getUsers method with void return type. Looks like you spied on the component method. – Sharikov Vladislav Jan 14 '20 at 10:10
6

I had similar issue and to make it work I used the arbitrary function (in the following code it's named done) inside of it


  it("should call getUsers and return list of users", async((done) => {
    // Arrange
    let response: User[] = [];

    // Act
    homeComponent.getUsers();

    fixture.detectChanges();
    fixture.whenStable().subscribe(() => {
        expect(homeComponent.listOfUsers).toEqual(response);
        done();
    });
  }));
Pini Cheyni
  • 5,073
  • 2
  • 40
  • 58
  • 1
    :) thanks, for me instead of "subscribe", "then" worked fine, litle change :- fixture.whenStable().then((response) => { expect(homeComponent.listOfUsers).toEqual(response); done(); }); – Shiv Gaurav Dec 25 '21 at 16:12
3

in your case you can use fakeAsync also used tick() to detect change. you can add time to tick also to indicate how log to wait. eg tick(1000)

Code is modified from Sharikov Vladislav

import { fakeAsync, getTestBed, TestBed, tick } from '@angular/core/testing';

it("should call getUsers and return list of users", fakeAsync(() => {
  const response: User[] = [];
  spyOn(userService, 'getUsers').and.returnValue(of(response))
  homeComponent.getUsers();
  tick();
  expect(homeComponent.listOfUsers).toEqual(response);
}));