Check out myAngular article series with live demos and Facebook group Angular - Advanced Topics

Angular Unit Testing

Torgeir "Tor" Helgevold
- JavaScript Developer and Blogger
Published: Sun Jan 17 2016

The new component based architecture in Angular is great for unit testing, so in the following article I will demonstrate how to unit test Angular 2.0 code using Jasmine and TypeScript.

A lot has changed in Angular, but as with most things, the underlying concepts are not that different at the end of the day. This is also true for unit testing as you'll see after going through my samples.

I have tried to identify a few common scenarios, and will through the following examples recommend unit testing patterns for these scenarios.

Simple Component

By far the simplest case is writing unit tests for a component without dependencies. These components are perhaps rare, but it's a good starting point. For the purposes of this article I've created a simple DisplayName component. As you can tell the component displays a person's full name by combining first name and last name.

import {Component,Input} from '@angular/core'; @Component({ selector:'display-name', templateUrl: './my-template.html' }) export class DisplayName{ @Input() firstName:string; @Input() lastName:string; fullName:string; generateFullName(){ this.fullName = this.firstName + ' ' + this.lastName; } }

The logic here is trivial, but I still think it deserves a unit test.

import {describe,expect,it,xit, inject, beforeEachProviders} from '@angular/core/testing'; import {DisplayName} from './display-name'; export function main() { describe('DisplayName', () => { beforeEachProviders(() => [ DisplayName ]); //Manually instantiate DisplayName it('should define full name', () => { let displayName = new DisplayName(); displayName.firstName = 'Joe'; displayName.lastName = 'Smith'; displayName.generateFullName(); expect(displayName.fullName).toBe('Joe Smith'); }); //Use DI to instantiate DisplayName it('should define full name2', inject([DisplayName], (displayName) => { displayName.firstName = 'Joe'; displayName.lastName = 'Smith'; displayName.generateFullName(); expect(displayName.fullName).toBe('Joe Smith'); })); }); }

The first thing to notice is that the entire test fixture is wrapped in a method called main. This is just a simple wrapper, but it seems to be the convention followed by the Angular team.

The sample above shows two variations on the test. The first one is the most straight forward since it simply instantiates the component class manually before calling the generateFullName method. Finally I have defined an expect on the fullName property.

In the second version of the test I have made use of DI to instantiate the component class. This requires a little bit more setup code since we have to tell Angular how to inject the component. As you can tell I am using the beforeEachProviders function in combination with inject to wire this up. You can basically think of this step as bootstrapping the code under test. Similar to how you would bootstrap your full application code.

Service with Dependencies

It might be overkill to use DI in the previous example, but when we introduce dependencies, you will hopefully see the benefits of DI in unit tests.

In the next example I have defined a CustomerService with a ProductService dependency.

//customer-service.ts import {ProductService} from './product-service'; import {Injectable} from '@angular/core' @Injectable() export class CustomerService{ productService:ProductService; constructor(productService:ProductService){ this.productService = productService; } printCustomerDetails(customerId){ let products = this.productService.getProductsByCustomerId(customerId).join(','); return 'Customer purchased: ' + products; } } //product-service.ts export class ProductService{ getProductsByCustomerId(customerId){ return ['Milk','Soda','Bread']; } }

In this case we can't instantiate the CustomerService without the ProductService, so it's easier to use DI when instantiating rather than doing it “long hand”.

import {describe,expect,it,xit, inject, beforeEachProviders} from '@angular/core/testing'; import {CustomerService} from './customer-service'; import {ProductService} from './product-service'; export function main() { describe('CustomerService', () => { beforeEachProviders(() => [ ProductService, CustomerService ]); it('should get customer details', inject([CustomerService], (customerService) => { let customerDetails = customerService.printCustomerDetails(1); expect(customerDetails).toBe('Customer purchased: Milk,Soda,Bread'); })); }); }

In the test I am registering the dependencies, so that the final injection of CustomerService will cascade down to include the dependency on ProductService.

Mock Dependencies

The previous example showed how to use DI in unit tests, but there is one problem with the implementation. When testing CustomerService we are directly hitting ProductService, which technically turns this into an integration test. Strictly speaking a “pure” unit test should focus on a unit in isolation and simulate the integration points. In the current implementation that might not be a big deal since the return values are hard coded. However in a more realistic implementation it's likely that ProductService would rely on external dependencies like a database or external api.

In the following example I will show how to override the ProductService dependency and replace it with a mock.

import {provide} from '@angular/core'; import {describe,expect,it,xit, inject, beforeEachProviders} from '@angular/core/testing'; import {CustomerService} from '../service-with-dependencies/customer-service'; import {ProductService} from '../service-with-dependencies/product-service'; export function main() { class ProductServiceMock{ getProductsByCustomerId(){ return ['Hamburger','Fries']; } } describe('CustomerService', () => { beforeEachProviders(() => [ provide(ProductService,{useClass: ProductServiceMock}), CustomerService ]); it('should get customer details', inject([CustomerService], (customerService) => { let customerDetails = customerService.printCustomerDetails(1); expect(customerDetails).toBe('Customer purchased: Hamburger,Fries'); })); }); }

The implementation is very similar, but notice how I am intercepting the provider registration by defining a mocked version called ProductServiceMock.

Markup Tests

I generally don't include markup testing in my unit tests, but Angular 2.0 makes this relatively easy through the TestComponentBuilder api. I have included a sample to demonstrate how to check for DOM changes based on events in the component. Specifically I will simulate typing into a textbox and bind the textbox value to a span tag.

import {Component, View} from '@angular/core'; @Component({ selector: 'bound-textbox' }) @View({ template: '<input [value]="text" (keyup)="typing($event)" /><span>{{text}}</span>' }) export class BoundTextbox { text = 'hello'; typing($event){ this.text = $event.target.value; } }

The Angular test api makes it very easy to instantiate the component and run assertions agains the rendered view. The key concept here is the TestComponentBuilder that lets me create an instance of the component with access to both the component class and DOM.

import {describe,expect,it,xit, beforeEachProviders, inject} from '@angular/core/testing'; import {TestComponentBuilder} from '@angular/compiler/testing'; import {BoundTextbox} from './bound-textbox'; export function main() { describe('Bound Textbox', () => { it('should update text', inject([TestComponentBuilder], (tcb:TestComponentBuilder) => { return tcb.createAsync(BoundTextbox) .then((fixture) => { fixture.detectChanges(); let compiled = fixture.elementRef.nativeElement; expect(compiled.querySelector('span')).toHaveText('hello'); fixture.componentInstance.typing({target:{value:'new val'}}); fixture.detectChanges(); expect(compiled.querySelector('span')).toHaveText('new val'); }); })); }); }

Testing Observables

Many common applications of observables (e.g. http) assume asynchronous execution, but observables are actually synchronous by default. As you may have guessed we can take advantage of this when mocking observables. If we replace asynchronous observables with synchronous mocks we can take the asynchronous part out of the equation when testing subscription handlers.

In the code sample below I want to test that the correct action is dispatched after the asynchronous getLogEntries method completes.

ngOnInit(){ this.logTailService.getLogEntries().subscribe((res) => this.store.dispatchAction(new LogAction('LOAD_ENTRIES', res.entries))); }

getLogEntries() returns an observable, but by mocking the asynchronous observable using a synchronous observable, we no longer have to worry about async execution in our test.

const mockLogEntries = {entries:[new LogEntry('error',2)]}; describe('LogDemo', () => { class LogTailServiceMock{ getLogEntries(){ return Observable.of(mockLogEntries); } } beforeEachProviders(() => [ LogDemo, Store, HTTP_PROVIDERS, provide(LogTailService,{useClass:LogTailServiceMock}) ]); it('should initialize', inject([LogDemo], (logDemo) => { spyOn(logDemo.store,'dispatchAction'); logDemo.ngOnInit(); expect(logDemo.store.dispatchAction).toHaveBeenCalledWith(new LogAction('LOAD_ENTRIES',mockLogEntries.entries)); })); });

The key to this is using Observable.of(..) to set up an observable that completes synchronously.

Hopefully this will get you started on unit testing in Angular. I will expand on this article over time to include even more patterns and scenarios.

As always my code is available on Github.

If you liked this article, share it with your friends on social media:

We also have a new Facebook group about advanced Angular topics.

I invite you to follow me on twitter