In this post I’ll explain a quick and easy way to test your angular components and services.
The complete source code for this article is available on github: https://github.com/ConsultDarryl/angular-simple-test-example
So lets dive in.
The first thing you need to do is create a new project and install the node modules. Let’s do this with the angular-cli.
ng new angular-simple-test-example npm install
Once npm has finished doing its thing, check everything is working:
ng test
Creating something to test
You are going to test the app.component class. Open the file (\src\app\app.component.ts) and copy the following code into it.
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app works!'; counter: number = 1; constructor() { } plusOne(): void { this.counter++; } }
Check the console to make sure everything is still working (ng test runs in the background, watching for changes, until you tell it to stop).
You are going to test the plusone() method.
plusOne() is a very simple method. All it does is increment a counter by 1.
Write a simple test
To begin testing, open the spec file (\src\app\app.component.spec.ts) and import AppComponent.
import { AppComponent } from './app.component';
Add some scaffolding.
describe('AppComponent', () => { it('should increment the counter', fakeAsync(() => { // Arrange // Act // Assert })); });
We’re using the AAA approach to testing – Arrange, Act, Assert.
Arrange
To start testing, create an instance of the AppComponent class.
let appComponent = new AppComponent();
You also need to initialise the counter.
appComponent.counter = 5;
Act
Lets do it… lets call the method you are trying to test.
appComponent.plusOne();
Assert
Did that work? Lets check.
expect(appComponent.counter).toBe(6);
This is Jasmine syntax. This line is saying “I expect the counter to be 6”.
That was easy. Here’s the full code.
it('should increment the counter', fakeAsync(() => { // Arrange let appComponent = new AppComponent(); appComponent.counter = 5; // Act appComponent.plusOne(); // Assert expect(appComponent.counter).toBe(6); }));
Paste it into the .spec.ts file and save the file.
Switch back to console. You should see 1 of 1 test passed.
Testing a component with injected dependencies
Lets extend the sample app by injecting a service into our component.
The service is just a wrapper around the @angular/http module. It retrieves customers from a REST endpoint.
import { Injectable } from '@angular/core'; import { Http } from '@angular/http'; import 'rxjs/add/operator/toPromise'; @Injectable() export class CustomerService { constructor(public http: Http) { } getCustomers(): any { return this.http.get("/customers").toPromise() .then(result => { // Success return result.json(); }) .catch(reason => { // Error console.log(reason); }); } }
Save the code in \src\app\customer.service.ts.
Open the component file. Import the service.
import { CustomerService } from './customer.service';
Inject the service.
constructor(public customerService: CustomerService) {
Add a method to call the service.
getNumberOfCustomers(): number { let customers = this.customerService.get(); return customers.length; }
Start testing
Open the test file: \src\app\app.component.spec.ts. Scroll to the bottom and add a new test.
it('should return the number of customers', fakeAsync(() => { //Arrange //Act //Assert }
Arrange
Create an instance of the CustomerService class. The CustomerService constructor takes an instance of the http object. However, you aren’t trying to test the http object so you are going to pass undefined.
let customerService = new CustomerService(undefined);
Next, you want to create an instance of AppComponent class. The AppComponent constructor takes an instance of the CustomerService object, so go ahead and pass the instance you just created.
let appComponent = new AppComponent(customerService);
You want the customerService.get() method to return a canned response so that you can test the AppComponent business logic.
You mock out the get() using the spyOn method (spyOn is a Jasmine function).
spyOn(customerService, 'get').and.returnValue(['fred', 'bob', 'sue']);
This line tells Jasmine to return [‘fred’, ‘bob’, ‘sue’] whenever customerService.get() is called.
Act
You are now ready to test your component. Go ahead and call getNumberOfCustomers().
let result = appComponent.getNumberOfCustomers();
Assert
Did that work? Lets check that 3 customers were returned.
expect(result).toBe(3);
That was easy. Here’s the full code.
// Advanced test, with a mocked http response it('should return the number of customers', fakeAsync(() => { // Arrange let customerService = new CustomerService(undefined); let appComponent = new AppComponent(customerService); // Mock out the getCustomers method of the customerService. spyOn(customerService, 'get').and.returnValue(['fred', 'bob', 'sue']); // Act let result = appComponent.getNumberOfCustomers(); // Assert expect(result).toBe(3); }));
Paste it into the .spec.ts file and save the file.
Switch back to console. You should see 2 of 2 tests passed.
Summary
In this article you’ve seen how easy it is to test angular. The angular documentation refers to these as isolated tests.
However, it’s often more productive to explore the inner logic of application classes with isolated unit tests that don’t depend upon Angular. Such tests are often smaller and easier to read, write, and maintain.
In my experience, this approach is all you need to test an angular app.
Angular provides the TestBed class. TestBed provides lots of useful, advanced tools, but I’ve rarely needed to use it.