Angular 2 Testing, Getting Started (Part 1)

Testing is important to building solid, production-ready applications. Today we’ll be discussing Angular 2 Testing.

We all know, having a solid test setup is crucial to know if our applications are functioning. Without a doubt we will introduce a bug when adding functionality, and usual – we’ll find out too late.

Often times we’ll skip testing because it takes a lot of work. It’s sometimes harder to write our tests than actually writing the code. Who wants to write tests anyway when we know they are going to break in the first place?

But we’re sold on testing. Testing is vital to the health of a solid system. By the end of this article, you’ll see how quick and easy it is to get started with Angular 2 testing.

Getting Started with Angular 2 Testing

In Angular 1 testing was integrated into the framework. But truth be told, it often came with a lot of headache and overhead. We had to set up our Angular environment for every single test.

However, Angular 2 testing is deeply integrated in the framework with the
angular2/testing package. You can bang out tests using the
TestComponentBuilder or
TestBed , but more on that shortly.

With TypeScript and Angular 2, our
Components are directly exported classes and usually have some dependencies – services, pipes, and other components. With Angular 1, we often would have to manually stub these instances out and provide our own mocks for them.

The magic of Angular 2, however removes the need for us to bootstrap our application when we’re writing our tests. We’ll initialize the components and inject our dependencies manually. We can _write tests just like we are calling any plain old method and checking the return value of the component’s properties_.

A _rapid_ review of Dependency Injection

The design pattern of Dependency Injection (or DI, for short) is simple and not tied to Angular 2 testing.

In a nutshell, the problem is all applications require dependency management, from low-level c applications up through our front-end applications, we’re dependent upon other libraries to build our apps.

There are different ways to handle managing dependencies in our application and dependency injection (DI) presents one possible solution.

Imagine we have a
Laptop class:

JavaScript

1

2

3

4

5

6

7

classLaptop{

constructor(){

this.keyboard=newKeyboard();

this.mouse=newMouse();

this.wifi=Wifi.getInstance();

}

}

A laptop instance is responsible for creating its own keyboard, mouse, and access to the WIFI instance (using a Singleton to get access to the Wifi object).

Everything is tucked neatly away in the Laptop class, right? Well… what if we want to write a test independent of the keyboard? What if the keyboard on our laptop is broken and we want to know if the laptop still functions? How can we test against the Keyboard when it’s integrated so deeply in the Laptop class?

Umm… we can’t… well, we can, but it’s a LOT of extra work (for good reason). Plus, setting up our tests like this can make for brittle tests and can often lead to more problems than solutions. This type of code is bad for testing and maintainability.

Wouldn’t it be so much easier if the Laptop class didn’t know anything about instantiating its dependencies, but instead were _handed_ the right ones?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

classLaptop{

constructor(keyboard,mouse,wifi){

this.keyboard=keyboard;

this.mouse=mouse;

this.wifi=wifi;

}

}

// or the typescript version

classLaptop{

constructor(

privatekeyboard:Keyboard,

privatemouse:Mouse,

privatewifi:Wifi

){}

}

The Laptop class doesn’t know anything about how to access its own dependencies – they are _injected_ through the constructor() function. Say hello to dependency injection.

Setting up mocks and testing this class is as simple as:

1

2

3

4

5

varlaptop=newLaptop(

newMockKeyboard(),

newMockMouse(),

newMockWifi()

);

Angular 2 Testing Tools

Jasmine

Jasmine allows us to write tests the way we want expect our apps to _behave._ It is a behaviour-driven development (BDD, for short )framework built for testing JavaScript code.

We don’t need to worry about setting it up if we start off your application with the [Angular QuickStart] bootstrap (the karma files in there already set up karma file).

Protractor

Protractor is used to write end-to-end tests (e2e). End-to-end tests are useful to mock a real user using our application as they are designed for us to pretend we are actually running the through the application in a browser, just like a real user. We’ll set up expectations and test against our assumptions about what the user sees.

Angular 2 Testing Platform

When we are ready to test how the classes interact with Angular and the DOM, we’ll use the Angular Testing Platform.

Although this isn’t the only type of test we’ll be writing, as we’ll want to write isolated unit tests as well, it’s the easiest and quickest way to test a live Component.

The Angular Testing Platform allows us to examine an instance of a class without any dependence on Angular or injected values.

Armed with the knowledge of the available tools for Angular 2 testing, let’s put them to use!

Where’s My Car – The Angular Test

Sorry, I just had to.

Let’s start off creating a small test for a simple Angular component, the CarComponent. Here’s what the component looks like.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

import{CarService}from‘./car.service’;

import{Component}from‘angular2/core’;

@Component({

selector:‘my-car’,

template:`

<h4>Where’smycar?</h4>

<div>{{carLocation}}</div>`

exportclassCarComponent{

carLocation:string;

constructor(privatecarService:CarService){}

findCar(){

this.carLocation=this.carService.findCar();

}

}

Let’s see how to initialize the component for testing. We want to use a mocked version of CarService because we are isolating the CarComponent to make the code more modular and independent.

Let’s implement our spec file. First, let’s import everything we need to get started with the testing.

1

2

3

4

5

import{ComponentFixture,TestBed}from'@angular/core/testing';

import{By}from'@angular/platform-browser';

import{DebugElement}from'@angular/core';

import{CarComponent}from‘./car.component';

We’ll look at _what_ and _why_ we need these core dependencies shortly.

Now let’s focus on the tests.

We’ll need an instance of the CarComponent as well as a fixture instance (we’ll get to this shortly). Let’s set these up in the spec file:

1

2

let comp:CarComponent;

let fixture:ComponentFixture<CarComponent>;

Next, we’ll use the Jasmine describe() function to define the CarComponent test suite:

1

2

3

describe('CarComponent',()=>{

// our test implementation will go here

})

The TestBed helper helps us create a test @NgModule. As this is the case, we can pretend the @NgModule for the test is created cleanly for every single test. We’ll pass an @NgModule-like configuration object into the configureTestingModule() function in a beforeEach() block:

1

2

3

4

5

6

7

8

9

10

11

describe('CarComponent',()=>{

beforeEach(()=>{

// TestBed takes @NgModule like configuration object

TestBed.configureTestingModule({

declarations:[CarComponent],// Declaring the test component

providers:[{provide:CarService,useValue:MockCarService}]// Mock Car Service that we will create later

});

// ...

});

});

Using this testbed, we can create the instance of our component by using the TestBed method createComponent():

1

2

3

4

5

6

7

8

9

10

11

12

13

describe('CarComponent',()=>{

beforeEach(()=>{

// TestBed takes @NgModule like configuration object

TestBed.configureTestingModule({

declarations:[CarComponent],// Declaring the test component

providers:[{provide:CarService,useValue:MockCarService}]// Mock Car Service that we will create later

});

fixture=TestBed.createComponent(CarComponent);

// CarComponent test instance

comp=fixture.componentInstance;

});

});

Let’s quickly break down what’s happening above. The TestBed handles all the magic. It is preconfigured with the default providers and declarable that we need to start testing – we don’t need to import everything separately.

We configure the TestBed by passing it the component we want to test along with the dependencies of that component.

Now that we have configured our TestBed, we need access to the fixture. The fixture provides all we need to perform testing on the component we’re focused on in this test.

Note: Once we call the createComponent method on the TestBed, we cannot configure it any further.

Let’s write our first test against the CarComponent:

1

2

3

4

5

6

7

8

9

10

11

12

it(‘should get location’,()=>{

// asking the component to get it's location

// is just like calling the `getLocation()` function:

comp.getLocation();

// Manually trigger the data binding

fixture.detectChanges();

// Our expectation

varcompiled=fixture.debugElement.nativeElement;

expect(compiled.querySelector('div')).toHaveText(’San Diego’);

}

Here, we tested our getLocation() method in three steps:

1. In our test, we call the getLocation() method on the instance of the CarComponent.
2. In order for Angular to _see_ any changes on the inputs, we’ll manually trigger it to look for any updates and resettle any data changes.
3. Finally, we get access to the DOM element our Component places on the page using debugElement.nativeElement. We then run our assertion using expect from Jasmine.

Finally, we’ll want to set up a mock CarService which allows us to control the response values and the environment we’ll run our tests within. Let’s create the MockCarService which will implement our getLocation() method the CarComponent uses:

1

2

3

4

5

6

7

8

classMockCarService{

publiclocation:string=‘San Diego’;

// This method is for when the CarComponent calls the getLocation method

getLocation(){

returnthis.location;

}

}

That’s it! We’re done and already testing our Angular components.

The Big Picture

When we’re writing Angular 2.x code on our next big application, using the Dependency Injection design pattern will save us tons of time and headaches.

Rule of thumb: every Angular component we’ll write should never instantiate any dependencies; instead we should pass our dependencies to it.