Tips & Tricks

Your First Unit Test Using Angular CLI, Karma, and WebStorm

This is a guest blog post prepared by Pete Heard.
Pete is a full-stack JavaScript developer who has spent over a decade learning to craft robust software using Test Driven Development and advanced Object Oriented Design. He is the founder of Logic Room, a consultancy that helps organisations create enterprise web and mobile software.

In this article we will use WebStorm 2016.3 with its built-in support for the Angular CLI and Karma test runner. We will write a single unit test which will show you how to mock a service and check that your component works before integrating your changes!

What we will cover:

Setting up your application

1. Install Node.js

Okay, first off let’s make sure you have downloaded and upgraded to the latest and greatest of Node.js by going here.

2. Install Angular CLI

Angular CLI is a wrapper around some tools (for example, Webpack) that you need to create, build and deploy an Angular 2 application. WebStorm offers an integration with Angular CLI that allows you to create a new Angular 2 project and generate new components, directives, and services from the IDE.

We want to get a shiny new copy of Angular CLI on our machine, so enter the following into a terminal window:
npm install -g angular-cli

3. Create an Angular 2 project with Angular CLI

Open WebStorm and click Create new project on the IDE Welcome screen. You will be given an option to start a new Angular CLI project.

1__image

Choose this option, fill in the details and WebStorm will kick off a CLI command ng init --name=MyFirstTest. Wait for WebStorm and the CLI to finish (notice the output in the tool window for indication and a line which reads ‘done’).

4. Set up Karma

Karma is a JavaScript test runner which means it will read code in a certain format (in our case written in Jasmine) and run it as a suite of tests. WebStorm provides neat integration with Karma, and our Angular project already has an initial configuration required to use Karma.

This setup is done through the common interface of the Karma configuration file called karma.conf.js. This file will tell Karma which locations the tests are in, which reporting mechanism to use, and which browser to use to execute the tests. Here is an example Karma config file from the Angular CLI project:

///...
basePath: '',
frameworks: ['jasmine', 'angular-cli'],
plugins: [
  require('karma-jasmine'),
  require('karma-chrome-launcher'),
  require('karma-remap-istanbul'),
  require('angular-cli/plugins/karma')
],
files: [
  { pattern: './src/test.ts', watched: false }
],
preprocessors: {
  './src/test.ts': ['angular-cli']
},
remapIstanbulReporter: {
  reports: {
    html: 'coverage',
    lcovonly: './coverage/coverage.lcov'
  }
},
angularCli: {
  config: './angular-cli.json',
  environment: 'dev'
},
///...

Now we need to configure WebStorm to invoke Karma and pass it the config when we want to run our tests. This is really easy – we need to create a new Karma run/debug configuration. Go to Run – Edit Configurations from the menu.

Next, click the plus icon and select Karma from the list.

6_image

Enter the name as Test, then choose the configuration file (project/karma.conf.js). In the box that says Karma package, make sure you set the correct path to the Karma module in the project’s node_module folder.

7_image

Click OK to apply those settings and exit. Then simply click Run – Test and your tests will execute from WebStorm!

8_image

We have now finished configuring WebStorm for use with Angular 2 and have a simple project ready to go.

Writing Our First Unit Test (And Stubbing a Service)

We are ready to write our test and some code! We are going to create a very simple unit test which will check that some value is extracted from an Angular 2 service and that this value is then attached to our component (which is known as a fixture

11_image

1. Add The Skeleton Service and its Test Double

Make two files my-service.ts and my-fake-service.ts and place them in the app folder. The code for each one is below.

src/app/my-service.ts – the skeleton of the real service is needed for type safety!

import { Injectable } from '@angular/core';
@Injectable()
export class MyService{
  getMessage(){}
}

src/app/my-fake-service.ts – the fake service is going to help us check that our component works.

import { Injectable } from '@angular/core';
@Injectable()
export class MyFakeService{
  getMessage(){
    return 'fake service';
  }
}

2. Update the Test File with Dependencies and a Test

Since Angular 1 we have seen the use of modules. These are high-level containers that house our individual features. Angular 2 has this concept too and in order to test any component we need to load it into a module beforehand. This is done (in part) by a handy built-in helper class called TestBed.

In the file src/app/app.component.spec.ts, we need to stub the dependency for the service in the TestBed configuration and then we will add the unit test.

We aren’t going to dig around in the view of the component to make our assertions. Instead we are going to check the instance of the component as this will be cleaner. We will check the componentinstance variable.

We are going to add our imports, new test and a provider declaration. The code listing below shows where to add them. Your code should look exactly like the following:

src/app/app.component.spec.ts

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';

import { MyService } from './my-service'
import { MyFakeService } from './my-fake-service'

describe('AppComponent', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      providers:[{provide:MyService,useClass:MyFakeService}] // --> new code
    });
    TestBed.compileComponents();
  });

  it('should create the app', async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    let app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  }));

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

  it('should render title in a h1 tag', async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    let compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h1').textContent).toContain('app works!');
  }));

  it('should attach message from service to component', async(() => {
    let fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    expect(fixture.componentInstance.message).toBe('fake service');
  }));

});

Tip: on large-scale projects, avoid digging around in views. Instead, create clean objects (viewmodels) that sit on your component and then check them, as this will keep your tests focused and increase the lifespan of your test suite. The views can be tested in isolation.

Once you have added this new code, you should see that the message property on the component is red. Hooray! We are at the red part of our red/green refactor cycle. You can run the test now and it will fail. In fact technically it won’t ever run since it won’t pass the compile stage, and a test that doesn’t compile is the same as a failing test!

Tip: Enable Auto-run option in the Karma tool window (second icon in the left pane) to re-run tests automatically as you make changes in your code.

3. Make the Test Pass (and Compile)

First, we need to get rid of these compile errors. Let’s start by adding a message property into our component called
message.

src/app/app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  public message : string;
}

Go back and run the test. Our test is still looking for something that doesn’t exist. Notice that we get the following:

Expected undefined to be 'fake service'.
	    at webpack:///Users/peteheard/Desktop/MyFirstTest/src/app/app.component.spec.ts:48:46

Okay, so now let’s go in and actually get the test passing. We do this by injecting our service into the component. But to do that, we need to also import the type. Your code should now look exactly as follows (notice the added reference to the service):

src/app/app.component.ts

import { Component } from '@angular/core';
import { MyService } from './my-service'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  public message : string;

  constructor(myService : MyService){
    this.message = myService.getMessage();
  }
}

Note: At this point you might be wondering why this.message in src/app/app.component.ts” is highlighted red. The reason is that the TypeScript service doesn’t yet know what the return type is and can’t tell at the moment that we have only implemented test code. When we wire up our final dependency, this will disappear.

Integrating Your Changes

Okay, you are now a bonafide TDDist, and when you do TDD well you should just be able to fire up your application and it will work first time. But let’s remember that we configured our dependency from a test context. We still need to wire up its real world counterpart. This involves 2 steps:

STEP 1: Update the real service my-service.ts to actually return something from its
getMessage function.

src/app/my-service.ts

import { Injectable } from '@angular/core';
@Injectable()
export class MyService{
  getMessage(){
    return 'real service';
  }
}

In the real world we wouldn’t have much untested code (such as with the real service). We are just learning how to mock/stub the service.

STEP 2: Add the dependency to the app.module.ts.

src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { MyService } from './my-service'

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [MyService],
  bootstrap: [AppComponent]
})
export class AppModule { }

STEP 3: Add The Property Onto The View

Finally, just add the property onto the view:
src/app/app.component.html

<h1>
{{title>
{{message}}
</h1>

Now just use WebStorm integrated npm menu to start the application. On the left-hand side double-click start in the npm tool window.

13_image

The application will launch. Then go to the url as shown below.

10_image

Conclusion

In this article we have learned a standardized and consistent way to create new Angular 2 using Angular CLI in WebStorm and to write and run unit tests with Karma.

Then we had a little TDD adventure and saw how to use a test double in place of a real service using Dependency Injection. We checked that an Angular 2 component was working with a stub which was set up from the test context. Then we wired up our real dependency and integrated our changes and ran the browser to check the final output.

I hope you have enjoyed this article. Happy clean-coding!

image description