Angular — Testing Guide (v4+)

Gerard Sans
Google Developer Experts
9 min readDec 5, 2016

Nine easy-to-follow examples using TestBed, fixtures, async and fakeAsync/tick.

Fluidscapes (Reza Ali)

In this post, we want to cover the most common unit tests to use for Angular Applications, like: Components, Services, Http and Pipes; but also some less known areas like: Directives, the Router or Observables. We will provide examples designed so you can use them as a quick reference. Don’t miss out the Testing Checklist to help you while creating your own tests.

Testing has been improved quite a lot from RC to final and even more after moving to NgModule. The Angular Core Team has worked hard to reduce boilerplate and be able to use not only Jasmine but other tests frameworks like Mocha.

There are few new APIs you should know about to get the best out of your tests. We will cover:

To test, all 29 specs or change the code, you can use this Stackblitz.

Find the latest Angular content following my feed at @gerardsans.

We used Jasmine but since Angular final you can also use other testing frameworks like Mocha.

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.

Setup and teardown

Jasmine offers four handlers to add our setup and teardown code: beforeEach, afterEach executed for each spec and beforeAll, afterAll executed once per suite.

use beforeEach and afterEach to do changes before and after each spec

A good practice to avoid code duplication on our specs is to refactor repetitive code into the setup.

Testing in Angular

Testing 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 in the actual tests.

This setup is just for reference.

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, we trigger Jasmine test-runner manually by calling onload.

Dependency Injection (DI)

TestBed, similarly to @NgModule helps us to set up the dependencies for our tests. We call TestBed.configureTestingModule passing our configuration. This information will then be used to resolve any dependencies. We can see an example below:

@NgModule({
declarations: [ ComponentToTest ]
providers: [ MyService ]
})
class AppModule { }
TestBed.configureTestingModule({
declarations: [ ComponentToTest ],
providers: [ MyService ]
});
//get instance from TestBed (root injector)
let service = TestBed.get(MyService);

inject, allows us to get dependencies at the TestBed level.

it('should return ...', inject([MyService], service => { 
service.foo();
}));

Component injector, allows us to get a dependency at the Component level.

@Component({ 
providers: [ MyService ]
})
class ComponentToTest { }
let fixture = TestBed.createComponent(ComponentToTest);
let service = fixture.debugElement.injector.get(MyService);

For dependencies defined at the Component level we need to use the Component injector as shown above. TestBed.get or inject won’t work.

Let’s see how we would use TestBed with the LanguagesService Component :

First we load the dependencies required for our tests with TestBed.configureTestingModule. Then on our spec we use inject to automatically instantiate each dependency.

We can now refactor the inject so we don’t repeat it for each spec.

Let’s see two examples of instantiating a Component. The first example, is synchronous and creates a fixture containing an instance of MyTestComponent.

// synchronous
beforeEach(() => {
fixture = TestBed.createComponent(MyTestComponent);
});

Second example, is asynchronous, as is using external templates and css, requiring XHR calls.

// asynchronous 
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MyTestComponent ],
}).compileComponents(); // compile external templates and css
}));

We can use async when dependencies involve asynchronous handling. This will internally create a zone and deal with any asynchronous processing.

Depending on your build setup your templates and css may be all inlined so you can safely use the synchronous approach

Testing Checklist

  • Which kind of test? Isolated, shallow or integration Test.
  • Can I use Mocks, Stubs or Spies? Dependencies should be covered by their own tests. Using them can boost your tests without losing efficacy.
  • Sync or Async? Does your test makes asynchronous calls? Uses XHR, Promises, Observables, etc. Is the Component using TemplateUrl or styleUrls or inline? Make sure you are using the corresponding APIs.

Let’s see how we can test the different building blocks of our application. We are going to skip all required imports for brevity. We added notes when necessary. They can be found here.

Testing Examples

Testing a Component

Let’s take a simple component that renders a greeting message using an @Input() property.

To test this component we will use a common setup using TestBed.

use TestBed 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 TestBed.createComponent to create an instance of our Greeter component. The component instance will be accessible within a fixture. This is its main API:

abstract class ComponentFixture {
debugElement; // test helper
componentInstance; // to access properties and methods
nativeElement; // to access DOM element
detectChanges(); // trigger component change detection
}

We used the name property to set up a value, trigger change detection with detectChanges and check the expected result when all asynchronous calls have ended using whenStable. In order to access the rendered text we used two different APIs filtering by a CSS selector (lines 22–23).

Other queries for debugElement are: query(By.all()) query(By.directive(MyDirective))

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 as a 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.

Note you can’t use fakeAsync if there are XHR calls involved. This is by design.

http.get() returns an Observable that we can subscribe to. We will cover Observables in more detail later on.

Testing using MockBackend

A more sensible approach is replacing HTTP calls with a MockBackend. In order to do this, we can use provide (line 10). This will allow us to mock our responses and avoid hitting the real backend, therefore, boosting our tests.

On our test we build our mocked response (lines 23–25) so when we finally make the call to our service it gets the expected results.

Note: we don’t need async as MockBackend behaves synchronously. Thanks to Pascal Precht ʕ•̫͡•ʔ for pointing it out.

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.

To trigger the click on the container we used the DOM API (recommended). We could also use fixture.debugElement.triggerEventHandler(‘click’).

In this test, we used fakeAsync and tick. Using fakeAsync all asynchronous processing will be paused until we call tick. This gives us greater control and avoids having to resort to nested blocks of Promises or Observables.

Using fakeAsync/tick we get greater control of the asynchronous code although it can’t be used with XHR.

Testing a Pipe

Pipes are functions that can 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 set up our specs very easily using inject.

In order to test our pipe we checked the common cases: it should work with empty string, it should capitalise and finally that it throws when not used with a string.

Note: We used an arrow function to capture exceptions 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 a few routes and an otherwise route pointing to home.

First route definition catches the initial route if none was provided redirecting to home (line 14). The second instantiates the Home Component (line 15); last one captures all remaining routes and redirects them also to home (line 16). Our tests will use these routes to check our expectations out.

We imported RouterTestingModule.withRoutes(routes) to initialise the Router instance with the routes for our tests (line 6). In the code above we tested, we can navigate to home using async, asyncFake/tick and done. Notice how we can write a nicer test using asyncFake as we don’t have to nest the promise handling.

fakeAsync/tick are great for complex asynchronous tests not involving XHR

Testing Observables

Observables are great to handle asynchronous tasks. They are used in few places in Angular 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 set up the next, error and complete callbacks on subscribe. As next callback will be called few times we have to set our expectations dynamically.

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.

You have all tests included in this post and more on the demo at Stackblitz.

That’s all I’ve got! 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

--

--

Gerard Sans
Google Developer Experts

Helping Devs to succeed #AI #web3 / ex @AWSCloud / Just be AWSome / MC Speaker Trainer Community Leader @web3_london / @ReactEurope @ReactiveConf @ngcruise