Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

Testing Components with Service Dependencies

Save for later
  • 300 min read
  • 2016-11-10 00:00:00

article-image

It is very common for your Angular 2 components to depend on a service that performs actions, such as fetching data. In this post we will look at testing components with service dependencies, and at testing asynchronous actions. We will be using Jasmine for our tests. If you have not read Getting Started Testing Angular 2 Components, I strongly suggest you do so before continuing.

Angular 2 Component with a Service Dependency

Continuing with our contact manager application, we need to have a ContactService that fetches data from a server:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class ContactService {

  constructor(private http: Http){ }

  getContacts() {
    return this.http.get('/contacts.json')
      .map(res => res.json());
  }

}

The Http service is injected here, and TypeScript will automatically assign the injected service to this.http. With this service ready to use, we are now ready to inject it into our ContactsComponent :

import { Component, OnInit } from '@angular/core';
import { ContactService } from '../shared/contact.service';

@Component({
  selector: 'contacts',
  template: `
<button (click)="getContacts()">Get Contacts</button>
<profile *ngFor="let profile of contacts" [info]="profile"></profile>
  `
})
export class ContactsComponent implements OnInit {

  contacts: Array<any>;

  constructor(private contactService: ContactService) { }

  ngOnInit() {
  }

  getContacts() {
    this.contactService.getContacts()
      .subscribe(data => {
        this.contacts = data;
      });
  }
}

We have an action set up, so when we click on the button, we make a call to our ContactService to fetch the data and assign the result. Once the call is resolved, the data will display.

Setting up your unit test

What we have to keep in mind is that we want to test our components in isolation. What this means is that instead of using the actual ContactService implementation, we create a MockContactService that returns mock data (array of Profile s).

let mockData = [
  {
    name: 'Victor Mejia',
    email: 'victor.mejia@example.com',
    phone: '123-456-7890'
  }
];

class MockContactService {
  getContacts(url) {
    return Observable.create((observer: Observer<Array<Profile>>) => {
      observer.next(mockData);
    });
  }
}

When configuring our testing module, we add a new property,providers, where we specify the usage of our mock service:

TestBed.configureTestingModule({
  declarations: [ContactsComponent],
  providers: [
    { provide: ContactService, useClass: MockContactService }
  ]
});

We can now go ahead and get handles on fixture, component, and element :

import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { ContactsComponent } from './contacts.component';
import { ContactService } from '../shared/contact.service';
import { Profile } from '../shared/profile.model';

import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';

let mockData = [
  {
    name: 'Victor Mejia',
    email: 'victor.mejia@example.com',
    phone: '123-456-7890'
  }
];

class MockContactService {
  getContacts(url) {
    return Observable.create((observer: Observer<Array<Profile>>) => {
      observer.next(mockData);
    });
  }
}

let fixture: ComponentFixture<ContactsComponent>;
let component: ContactsComponent;
let element: HTMLElement;

describe('Component: Contacts', () => {
  beforeEach(async(() => {

    TestBed.configureTestingModule({
      declarations: [ContactsComponent],
      providers: [
        { provide: ContactService, useClass: MockContactService }
      ]
    });

    TestBed.compileComponents()
      .then(() => {
        fixture = TestBed.createComponent(ContactsComponent);
        component = fixture.debugElement.componentInstance;
        element = fixture.debugElement.nativeElement
      });

  }));
});

Ensuring calls to our service

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime

A good test to always perform is to ensure that your actions are making the correct calls to your service. To do so, we can spy on the getContacts() function on the service, calling the component action and then ensuring that the function was indeed called:

describe('getContacts', () => {
  it('should make a call to contactService.getContacts()', () => {
    spyOn(component.contactService, 'getContacts').and.callThrough();

    component.getContacts();

    expect(component.contactService.getContacts).toHaveBeenCalled();
  });
});

Ensuring data is set

A follow-up test to be performed is to ensure that the data is being set on the component after the call to the API is resolved. Since our call to getContacts() is performing an asynchronous action, we should use the async function in the it:

it('should set the contacts property after fetching data', async(() => {
  ...
}));

It wraps the test function in an asynchronous “test zone”. Basically, it automatically completes when the asynchronous actions are complete.

Next, we can make a call to component.getContacts() . However, we don’t want to run our specs until after that call has been resolved. There is a useful function we can use in our fixture, fixture.whenStable(). This returns a promise that resolves after asynchronous activity. Our test should now look as follows:

it('should set the contacts property after fetching data', async(() => {
  component.getContacts();

  fixture.whenStable().then(() => {
    expect(component.contacts).toEqual(mockData);
  });
}));

We simply run a check to ensure that the contacts property is set to what the API call returns.

Finer Async Control

There are times when you want finer control, such as dealing with time intervals, and so on. To do so, we can simply use the fakeAsync in conjunction with the tick() function to simulate the passage of time.

it('asynchronous timed test...', fakeAsync(() => {
  component.asyncActionWithTime();

  tick(2000); // "advance" 2 seconds

  expect(...).toBe(...);
}));

Conclusion

Angular 2 has wonderful APIs that make it really easy to test your components. We have seen how to test components with service dependencies, along with asynchronous actions. Time to start writing tests!

Modal Close icon
Modal Close icon