Angular — Unit Testing recipes (v2+)
Recipes for Angular Unit Testing using Jasmine
Angular — Testing Guide (v4+)
Up-to-date version of Angular — Unit Testing recipes. Read it to learn about the latest Angular APIs.
Keep reading to see the old versions of the testing API. The Jasmine section and custom matchers are still valid though.
Angular was designed with testability in mind as its predecessor and it provides multiple options to support Unit Testing. In this article we will show you how to setup Jasmine and write some common unit tests for your Angular Applications. We will cover:
- Introducing Jasmine: main concepts, features, setup and tear down, default matchers and custom matchers.
- Testing in Angular: setup and dependency injection.
- Unit Testing recipes for Angular: Components, Services, Http and MockBackend, Directives, Pipes, Routes, Observables and EventEmitter.
You can navigate to each recipe using the links above or skip to the second section if you are already familiar with Jasmine.
A fully working example including all specs can be found here (Plunker). Find the latest Angular content following my feed at @gerardsans.
Introduction to Jasmine
Jasmine is an open source testing framework from Pivotal Labs that uses behaviour-driven notation resulting in a fluent and improved testing experience.
Main concepts
- Suites — describe(string, function) functions, take a title and a function containing one or more specs.
- Specs — it(string, function) functions, take a title and a function containing one or more expectations.
- Expectations — are assertions that evaluate to true or false. Basic syntax reads expect(actual).toBe(expected)
- Matchers — are predefined helpers for common assertions. Eg: toBe(expected), toEqual(expected). Find a complete list here.
Note that .toEqual() does a deep comparison while .toBe() is just a reference equality.
Jasmine Features
Since version 2.0 major upgrade (Dec 2013) there have been few releases. Jasmine 2.4.1, was released just last December. See below the main additions since 2.0:
- jasmine.stringMatching(string|regExp) matcher — partial match for strings. It also accepts Regular Expressions as an argument.
- jasmine.arrayContaining(array) matcher — partial match when an expectation only requires some of the values in an array to be present
- jasmine.anything() matcher — matches any value that is neither null or undefined.
- pending(string) function — we can now pass in a reason as a text to be shown by the reporter.
- fail(string|Error) function — fail function will cause a spec to fail, taking a message or an Error object as a parameter.
- focused specs/suites — by using fit and fdescribe you can decide which specs or suites to be run.
- one-time setup and teardown — this can be used by calling beforeAll and afterAll.
- disabled specs/suites — you can selectively disable specs or suites with xit and xdescribe (shown as pending).
Find all release details here.
Setup and teardown
A good practice to avoid code duplication on our specs is to include setup code.
use beforeEach and afterEach to do changes before and after each spec
Jasmine offers four handlers to add our setup and teardown code: beforeEach, afterEach executed for each spec and beforeAll, afterAll executed once per suite.
Default Matchers
These are Jasmine’s default set of matchers.
Check out Jasmine-Matchers for some additional matchers for Arrays, Booleans, Browser, Numbers, Exceptions, Strings, Objects and Dates.
Custom Matchers
Sometimes you can improve your specs or failure messages using a custom matcher library. If you feel you are doing a lot of boilerplate in your tests this might help you.
Let’s see how to create a myCustomMatchers library containing only one simplified matcher: toBeAllowedToDrive. A matcher must be within a factory object containing a compare function. Its signature is compare(actual, expected) returning an object like { pass: boolean, message: string }. This implementation will work both for expect(age).toBeAllowedToDrive() and (age).not.toBeAllowedToDrive().
We can see how we improved specs readability on the code below. Messages will also improve future maintenance and debugging experience.
Testing in Angular
Setup
There are few options to setup your environment. You can use Jasmine’s SpecRunner.html from the standalone distribution to start or create your own. You can also integrate it with a test runner like Karma. We are not going to cover all combinations as we are more interested on the actual tests.
This setup is just for reference and will only work with Plunker.
We load Jasmine dependencies followed by Angular ones. We are using a System.js and TypeScript setup. We use Promise.all() to load our specs in one go and, once all specs are available, trigger Jasmine test-runner. Don’t forget to include testing.dev.js.
Check this url to find out which modules, barrels and bundles are required for your setup.
Dependency Injection (DI)
In order to use Angular components in our tests we need to include our dependencies as we do in our application using bootstrap. This information will then be used by the Dependency Injection engine to resolve all references. In order to do this we are going to use: beforeEachProviders, inject and injectAsync. All imported from ‘angular2/testing’.
Let’s see how we would use them with the LanguagesService Component :
First we load the dependencies required for our tests with beforeEachProviders. This will setup a fresh new Injector instance before each test using the dependencies required as an Array. Then on our spec we used inject to automatically instantiate each dependency. For simple tests we could also use this alternative below but as a general rule use inject to use the full potential of DI.
Two common usages of inject are:
Finally we can use async when dependencies involve asynchronous handling. This will internally create a zone and deal with any asynchronous processing.
Let’s see how we can test the different building blocks of our application. We are going to skip all required imports in this post for brevity. We added notes when necessary. They can be found here.
Unit Testing recipes for Angular (v2+)
Testing a Component
Let’s take a simple component that renders a greeting message using an @Input() property.
To help testing this component we will use a common setup using beforeEachProviders.
use beforeEachProviders() to load the corresponding dependencies so they are available during your tests
A common practice is to use beforeEach to refactor our tests. By doing it we avoid having to repeat some code like inject for each test. This will also simplify our specs.
We used createAsync() from TestComponentBuilder to create an instance of our Greeter component returning a Promise. The component will then be created within a test fixture. This is its main API:
abstract class ComponentFixture {
debugElement; // test helper
componentInstance; // access properties and methods
nativeElement; // access DOM
detectChanges(); // trigger component change detection
}
We used the name property to set up a value, trigger change detection and check the expected result.
We used Jasmine’s asynchronous testing support using done introduced in Jasmine 2. If you are not familiar with it read this. We are going to use it few times.
Testing a Service
LanguagesService, has only one method that returns an array of available languages for the application.
Similar to our previous example we instantiate the service using beforeEach. As we said, this is a good practice even if we only have one spec. On this occasion we are checking each individual language and the total count.
Testing using Http
We usually don’t want to make http calls during our tests but we are going to show it for reference. We have replaced our initial service, LanguageService for LanguageServiceHttp.
In this case it uses http.get() to read a json file. We then used Observable.map() to transform the response into the final result using json().
Our test looks very similar to the previous one. The main difference is the use of an asynchronous test as we did with the component due to the subscribe.
http.get() returns an Observable that we can subscribe to. We will cover Observables in more detail later on.
Testing using MockBackend
A more real use case is replacing http calls with a MockBackend. In order to do this we can use provide to create a new instance every time we instantiate Http using useFactory and providing the necessary plumbing (lines 9–11). This will allow us to mock our responses and avoid hitting the real backend boosting our tests.
On our test we build our mocked response (lines 22–24) so when we finally make the call to our service it gets the expected results.
Testing a Directive
Directives in Angular are a specific type of component with usually no accompanying view. We will use an attribute directive, logClicks, that logs how many clicks we do on the host element so you can grasp the idea.
In order to test this directive we decided to create a Container component. We will set it up so it will act as our host reproducing the events emitted by our directive.
We used beforeEach to separate the logic for creating the component from the tests. This part can now be used for all specs.
We used injectAsync as the fixture is created asynchronously. injectAsync requires that we return a Promise. In this instance we are returning the Promise returned by createAsync. Only after it is resolved it will start running the specs.
We can also use injectAsync returning a Promise to deal with asynchronous tests
Testing a Pipe
Pipes are functions that transform input data into a user readable format. We will write a custom capitalise pipe, capitalise, using the standard String.toUpperCase(). This is only for simplicity as angular has its own UpperCasePipe implementation.
Pipes are just plain classes that can be injected so we can setup our specs very easily using inject.
In order to test our pipe we checked the common cases: it throws when not used with a string, it should work with empty string and finally that it should capitalise. Just note that we had to use an arrow function to capture the exception within expect.
Testing Routes
Routes are sometimes left out but it is usually seen as a good practice for double-entry bookkeeping. In our example, we will use a simple route configuration with only few routes and a default pointing to home.
Our tests will use these routes above to check our expectations out. Last one captures all remaining routes and redirects them to home.
In order to test the routes we need to include few internal dependencies (lines 6–9). Notice how we defined our root component (line 9). These will probably be refactored soon but you can use these for now. We instantiated the Router and Location within beforeEach leaving our tests more readable.
We are using navigate(params) and navigateByUrl(url) both returning a Promise. We check that when we navigate to ‘/home’ our location will change accordingly. Finally, we check that any other routes redirect you to home. We used Jasmine’s asynchronous tests as we did before.
Testing Observables
Observables are used in Angular to handle asynchronous tasks. They can be seen in few places like Http, Form controls, validations or behind EventEmitter. We will use the Observable below to show how we can test their behaviour.
We created an Observable that emits 1, 2, 3 and completes. In order to test it we setup the next, error and complete callbacks on subscribe. As next callback will be called few times we have to set our expectations dynamically. Note how we used Jasmines asynchronous test again with done.
Testing EventEmitters
EventEmitters are used in Angular to communicate events between Components. We created a counter component, Counter, that allows us to increment or decrement an initial value of zero. Each time that we do that the new value will be pushed using an EventEmitter exposed as changes.
The setup will be very similar to Observables.
In this case we check that we can increment or decrement using subscribe on the EventEmitter as it exposes an Observable. We trigger the different values by calling the change method and check our expectations within the next callback.
So that’s all I’ve got for now! Thanks for reading! Have any questions? Ping me at @gerardsans
Want more?
If you need more examples please feel free to contact me at gerard_dot_sans_at_gmail_dot_com or head to Angular Unit Tests in GitHub!
Further Reading
- Slides: Testing Strategies with Angular or video by Julie Ralph, @SomeJulie
- Angular Connect Media Slides and Videos from all sessions