How to test component error handling with observable service

BlackHoleGalaxy picture BlackHoleGalaxy · Apr 9, 2017 · Viewed 7.3k times · Source

Context

I'm trying to test a component. In another post I asked about testing the direct function call. Here I'm focusing, using same example component, on the error handling of my component.

I want to test, when my service returns an observable error, that my component properly calls console.error(error).

How to "create" this error trigger and test my component handles it properly. I heard about spies could do that but I don't know where to setup this spy.

I want to simulate the case where the http request on the service could fail. For any reason or any error code.

Code

Here is the code for component, stub service and my spec files.

Component

import { Component, OnInit, ViewContainerRef } from '@angular/core';

import { UserManagementService } from '../../shared/services/global.api';
import { UserListItemComponent } from './user-list-item.component';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html'
})
export class UserListComponent implements OnInit {
  public userList = [];

  constructor(
    private _userManagementService: UserManagementService,    
  ) { }

  ngOnInit() {
    this.getUserList();
  }

  onRefreshUserList() {
    this.getUserList();
  }

  getUserList(notifyWhenComplete = false) {
    this._userManagementService.getListUsers().subscribe(
      result => {
        this.userList = result.objects;
      },
      error => {
        console.error(error); // That's the part of my component I want to test     
      },
      () => {
        if (notifyWhenComplete) {
          console.info('Notification');
        }
      }
    );
  }
}

Component spec file

import { NO_ERRORS_SCHEMA } from '@angular/core';
import {
  async,
  fakeAsync,
  ComponentFixture,
  TestBed,
  tick,
  inject
} from '@angular/core/testing';

import { Observable } from 'rxjs/Observable';

// Components
import { UserListComponent } from './user-list.component';

// Services
import { UserManagementService } from '../../shared/services/global.api';
import { UserManagementServiceStub } from '../../testing/services/global.api.stub';

let comp:    UserListComponent;
let fixture: ComponentFixture<UserListComponent>;
let service: UserManagementService;

describe('UserListComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [UserListComponent],
      imports: [],
      providers: [
        {
          provide: UserManagementService,
          useClass: UserManagementServiceStub
        }
      ],
      schemas: [ NO_ERRORS_SCHEMA ]
    })
    .compileComponents();
  }));

  tests();
});

function tests() {
  beforeEach(() => {
    fixture = TestBed.createComponent(UserListComponent);
    comp = fixture.componentInstance;

    service = TestBed.get(UserManagementService);
  });

  it(`should be initialized`, () => {
    expect(fixture).toBeDefined();
    expect(comp).toBeDefined();
  });

  it(`should get the user List after ngOnInit`, async(() => {
    fixture.detectChanges();

    expect(comp.userList.length).toBe(3, 'user list exists after init');
  }));
}

Stub service

import { Observable } from 'rxjs/Observable';

export class UserManagementServiceStub {
  getListUsers() {
    return Observable.from([      
      {
        count: 3, 
        objects: 
        [
          {
            id: "7f5a6610-f59b-4cd7-b649-1ea3cf72347f",
            name: "user 1",
            group: "any"
          },
          {
            id: "d6f54c29-810e-43d8-8083-0712d1c412a3",
            name: "user 2",
            group: "any"
          },
          {
            id: "2874f506-009a-4af8-8ca5-f6e6ba1824cb", 
            name: "user 3",
            group: "any"
          }
        ]
      }
    ]);
  }
}

Answer

Steve Land picture Steve Land · Apr 17, 2017

If I'm correct in understanding that you just need a way to return an error from a stubbed service, you can just create a separate stub which returns an Observable error, and then use that in the test that checks for error handling:

export class UserManagementServiceErrorStub {
  getListUsers() {
    return Observable.throw(new Error('Test error'));
  }
}

There is a really nice article on testing Angular services using Observables here: https://web.archive.org/web/20191214083526/http://www.zackarychapple.guru/angular2/2016/11/25/angular2-testing-services.html

Update - 01/06/2019 for RxJS 6

import { throwError } from 'rxjs'; 

class UserManagementServiceErrorStub {
  getListUsers() {
    return throwError(new Error('Test error'));
  }
}