12 min read

Testing (and more specifically, unit testing) is meant to be carried out by the developer as the project is being developed. In this article, we will see how to implement testing tools to perform proper unit testing for your application classes and components.

This tutorial is an excerpt taken from the book Learning Angular (Second Edition) written by Christoffer Noring, Pablo Deeleman.

When venturing into unit testing in Angular, it’s important to know what major parts it consists of. In Angular these are:

  • Jasmine, the testing framework
  • Angular testing utilities
  • Karma, a test runner for running unit tests, among other things
  • Protractor, Angular’s framework for E2E testing

Configuration and setting up of Angular CLI

In terms of configuration, when using the Angular CLI, you don’t have to do anything to make it work. You can, as soon as you scaffold a project, run your first test and it will work.  The Angular CLI is using Karma as the test runner. What we need to know about Karma is that it uses a karma.conf.js file, a configuration file, in which a lot of things are specified, such as:

  • The various plugins that enhance your test runner.
  • Where to find the tests to run?  It should be said that there is usually a files property in this file specifying where to find the application and the tests. For the Angular CLI, however, this specification is found in another file called src/tscconfig-spec.json.
  • Setup of your selected coverage tool, a tool that measures to what degree your tests cover the production code.
  • Reporters report every executed test in a console window, to a browser, or through some other means.
  • Browsers run your tests in: for example, Chrome or PhantomJS.

Using the Angular CLI, you most likely won’t need to change or edit this file yourself. It is good to know that it exists and what it does for you.

Angular testing utilities

The Angular testing utilities help to create a testing environment that makes writing tests for your various constructs really easy. It consists of the TestBed class and various helper functions, found under the @angular/core/testing namespace. Let’s have a look at what these are and how they can help us to test various constructs. We will shortly introduce the most commonly used concepts so that you are familiar with them as we present them more deeply further on:

  • The TestBed class is the most important concept and creates its own testing module. In reality, when you test out a construct to detach it from the module it resides in and reattach it to the testing module created by the TestBed. The TestBed class has a configureTestModule() helper method that we use to set up the test module as needed. The TestBed can also instantiate components.
  • ComponentFixture is a class wrapping the component instance. This means that it has some functionality on it and it has a member that is the component instance itself.
  • The DebugElement, much like the ComponentFixture, acts as a wrapper. It, however, wraps the DOM element and not the component instance. It’s a bit more than that though, as it has an injector on it that allows us to access the services that have been injected into a component.

This was a brief overview of our testing environment, the frameworks, and libraries used. Now let’s discuss component testing.

Introduction to component testing

A usual method of operation for doing anything Angular is to use the Angular CLI. Working with tests is no different. The Angular CLI lets us create tests, debug them, and run them; it also gives us an understanding of how well our tests cover the code and its many scenarios.

Component testing with dependencies

We have learned a lot already, but let’s face it, no component that we build will be as simple as the one we wrote in the preceding section. There will almost certainly be at least one dependency, looking like this:

@Component({})
export class ExampleComponent {
  constructor(dependency:Dependency) {}
}

We have different ways of dealing with testing such a situation. One thing is clear though: if we are testing the component, then we should not test the service as well. This means that when we set up such a test, the dependency should not be the real thing. There are different ways of dealing with that when it comes to unit testing; no solution is strictly better than the other:

  • Using a stub means that we tell the dependency injector to inject a stub that we provide, instead of the real thing.
  • Injecting the real thing, but attaching a spy, to the method that we call in our component.

Regardless of the approach, we ensure that the test is not performing a side effect such as talking to a filesystem or attempting to communicate via HTTP; we are, using this approach, isolated.

Using a stub to replace the dependency

Using a stub means that we completely replace what was there before. It is as simple to do as instructing the TestBed in the following way:

TestBed.configureTestingModule({
  declarations: [ExampleComponent]
  providers: [{ 
    provide: DependencyService, 
    useClass: DependencyServiceStub 
  }]
});

We define a providers array like we do with the NgModule, and we give it a list item that points out the definition we intend to replace and we give it the replacement instead; that is our stub.

Let’s now build our DependencyStub to look like this:

class DependencyServiceStub {
  getData() { return 'stub'; }
}

Just like with an @NgModule, we are able to override the definition of our dependency with our own stub. Imagine our component looks like the following:

import { Component } from '@angular/core';
import { DependencyService } from "./dependency.service";

@Component({
  selector: 'example',
  template: `
  
{{ title }}
` }) export class ExampleComponent { title: string; constructor(private dependency: DependencyService) { this.title = this.dependency.getData(); } }

Here we pass an instance of the dependency in the constructor. With our testing module correctly set up, with our stub, we can now write a test that looks like this:

it(`should have as title 'stub'`, async(() => {
  const fixture = TestBed.createComponent(AppComponent);
  const app = fixture.debugElement.componentInstance;
  expect(app.title).toEqual('stub');
}));

The test looks normal, but at the point when the dependency would be called in the component code, our stub takes its place and responds instead. Our dependency should be overridden, and as you can see, the expect(app.title).toEqual('stub') assumes the stub will answer, which it does.

Spying on the dependency method

The previously-mentioned approach, using a stub, is not the only way to isolate ourselves in a unit test. We don’t have to replace the entire dependency, only the parts that our component is using. Replacing certain parts means that we point out specific methods on the dependency and assign a spy to them. A spy is an interesting construct; it has the ability to answer what you want it to answer, but you can also see how many times it is being called and with what argument/s, so a spy gives you a lot more information about what is going on. Let’s have a look at how we would set a spy up:

beforeEach(() => {
  TestBed.configureTestingModule({
    declarations: [ExampleComponent],
    providers: [DependencyService]
  });
dependency = TestBed.get(DependencyService);

spy = spyOn( dependency,'getData');
fixture = TestBed.createComponent(ExampleComponent);
})

Now as you can see, the actual dependency is injected into the component. After that, we grab a reference to the component, our fixture variable. This is followed by us using the TestBed.get('Dependency') to get hold of the dependency inside of the component. At this point, we attach a spy to its getData() method through the spyOn( dependency,'getData') call.

This is not enough, however; we have yet to instruct the spy what to respond with when being called. Let us do just that:

spyOn(dependency,'getData').and.returnValue('spy value');

We can now write our test as usual:

it('test our spy dependency', () => {
  var component = fixture.debugElement.componentInstance;
  expect(component.title).toBe('spy value');
});

This works as expected, and our spy responds as it should. Remember how we said that spies were capable of more than just responding with a value, that you could also check whether they were invoked and with what? To showcase this, we need to improve our tests a little bit and check for this extended functionality, like so:

it('test our spy dependency', () => {
  var component = fixture.debugElement.componentInstance;
  expect(spy.calls.any()).toBeTruthy();
})

You can also check for the number of times it was called, with spy.callCount, or whether it was called with some specific arguments: spy.mostRecentCalls.args or spy.toHaveBeenCalledWith('arg1', 'arg2'). Remember if you use a spy, make sure it pays for itself by you needing to do checks like these; otherwise, you might as well use a stub.

Spies are a feature of the Jasmine framework, not Angular. The interested reader is urged to research this topic further at http://tobyho.com/2011/12/15/jasmine-spy-cheatsheet/.

Async services

Very few services are nice and well-behaved, in the sense that they are synchronous. A lot of the time, your service will be asynchronous and the return from it is most likely an observable or a promise. If you are using RxJS with the Http service or HttpClient, it will be observable, but if using the fetch API, it will be a promise. These are two good options for dealing with HTTP, but the Angular team added the RxJS library to Angular to make your life as a developer easier. Ultimately it’s up to you, but we recommend going with RxJS.

Angular has two constructs ready to tackle the asynchronous scenario when testing:

  • async() and whenStable(): This code ensures that any promises are immediately resolved; it can look more synchronous though
  • fakeAsync() and tick(): This code does what the async does but it looks more synchronous when used

Let’s describe the async() and whenStable() approaches. Our service has now grown up and is doing something asynchronous when we call it like a timeout or an HTTP call. Regardless of which, the answer doesn’t reach us straightaway. By using async() in combination with whenStable(), we can, however, ensure that any promises are immediately resolved. Imagine our service now looks like this:

export class AsyncDependencyService {
  getData(): Promise {
    return new Promise((resolve, reject) => {
      setTimeout(() => { resolve('data') }, 3000);
    })
  }
}

We need to change our spy setup to return a promise instead of returning a static string, like so:

spy = spyOn(dependency,'getData')
.and.returnValue(Promise.resolve('spy data'));

We do need to change inside of our component, like so:

import { Component, OnInit } from '@angular/core';
import { AsyncDependencyService } from "./async.dependency.service";
@Component({
  selector: 'async-example',
  template: `
  
{{ title }}
` }) export class AsyncExampleComponent { title: string; constructor(private service: AsyncDependencyService) { this.service.getData().then(data => this.title = data); } }

At this point, it’s time to update our tests. We need to do two more things. We need to tell our test method to use the async() function, like so:

it('async test', async() => {
  // the test body
})

We also need to call fixture.whenStable() to make sure that the promise will have had ample time to resolve, like so:

import { TestBed } from "@angular/core/testing";
import { AsyncExampleComponent } from "./async.example.component";
import { AsyncDependencyService } from "./async.dependency.service";

describe('test an component with an async service', () => {
  let fixture;
  
  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [AsyncExampleComponent],
      providers: [AsyncDependencyService]
    });
fixture = TestBed.createComponent(AsyncExampleComponent);
});

it('should contain async data', async () => {
const component = fixture.componentInstance;
fixture.whenStable.then(() => {
fixture.detectChanges();
expect(component.title).toBe('async data');
});
});
});

This version of doing it works as it should, but feels a bit clunky. There is another approach using fakeAsync() and tick(). Essentially, fakeAsync() replaces the async() call and we get rid of whenStable(). The big benefit, however, is that we no longer need to place our assertion statements inside of the promise’s then() callback. This gives us synchronous-looking code. Back to fakeAsync(), we need to make a call to tick(), which can only be called within a fakeAsync() call, like so:

it('async test', fakeAsync() => {
  let component = fixture.componentInstance;
  fixture.detectChanges();
  fixture.tick();
  expect(component.title).toBe('spy data');
});

As you can see, this looks a lot cleaner; which version you want to use for async testing is up to you.

Testing pipes

A pipe is basically a class that implements the PipeTransform interface, thus exposing a transform() method that is usually synchronous. Pipes are therefore very easy to test. We will begin by testing a simple pipe, creating, as we mentioned, a test spec right next to its code unit file. The code is as follows:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'formattedpipe'
})
export class FormattedPipe implements PipeTransform {
  transform(value: any, ...args: any[]): any {
      return "banana" + value;
    }
}

Our code is very simple; we take a value and add banana to it. Writing a test for it is equally simple. The only thing we need to do is to import the pipe and verify two things:

  • That it has a transform method
  • That it produces the expected results

The following code writes a test for each of the bullet points listed earlier:

import FormattedTimePipe from './formatted-time.pipe';
import { TestBed } from '@angular/core/testing';
describe('A formatted time pipe' , () => {
let fixture;
  beforeEach(() => {
    fixture = new FormattedTimePipe();
  })
  // Specs with assertions
  it('should expose a transform() method', () => {
    expect(typeof formattedTimePipe.transform).toEqual('function');
  });
it('should produce expected result', () => {
expect(fixture.transform( 'val' )).toBe('bananaval');
})
});

In our beforeEach() method, we set up the fixture by instantiating the pipe class. In the first test, we ensure that the transform() method exists. This is followed by our second test that asserts that the transform() method produces the expected result.

We saw how to code powerful tests for our components and pipes. If you found this post useful, be sure to check out the book Learning Angular (Second Edition) to learn about mocking HTTP responses and unit testing for routes, input, and output, directives, etc.

Read Next

Getting started with Angular CLI and build your first Angular Component
Building Components Using Angular
Why switch to Angular for web development – Interview with Minko Gechev


Subscribe to the weekly Packt Hub newsletter. We'll send you the results of our AI Now Survey, featuring data and insights from across the tech landscape.

LEAVE A REPLY

Please enter your comment!
Please enter your name here