Introduction to component testing

Our usual method of operation for doing anything Angular by now, is to use the Angular CLI. Working with tests is no different. The Angular CLI lets us create tests, debug them, and run them; it also gives us an understanding of how well our tests cover the code and its many scenarios. Let's have a quick look at how we can get going with some unit testing using the Angular CLI, and try to understand what is given to us by default.

If you want to code along with this chapter, you can either take an old Angular project and add tests to it or create a new standalone project, if you want to focus on practice testing only. The choice is yours.

If you opt for creating a new project, then type the following to scaffold it:

ng new AngularTestDemo
// go make coffee :)
cd AngularTestDemo
ng serve

The Angular CLI comes with testing already set up, so the only thing we need to do is follow in its footsteps and add more tests, but let's first examine what we've got and learn some neat commands to make it easier to work with testing.

The first thing we want to do is the following:

  • Investigate the tests that the Angular CLI has given us
  • Run the tests

By looking in the scaffolded directory/app, we see the following:

app.component.ts
app.component.spec.ts

We see a component being declared, together with a unit test. This means we get tests with our components, which is very good news as it saves us a bit of typing.

Let's have a look at the test that was given to us:

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

describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));

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

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

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

That's a lot of code, but we will break it down. We see the testing setup, at the beginning of the file, with three different tests being written. Let's have a look at the setup phase first:

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));

Here we are calling beforeEach(), as we normally do in a Jasmine test, to run code before each test actually happens. Inside of the beforeEach(), we call the TestBed.configureTestingModule() method, with an object as an argument. The object resembles the object that we give the NgModule as an argument. This means we can take our knowledge of NgModule and how to set up Angular modules and apply that to how to set up testing modules, because it is really one and the same. Looking at the code, we can see that we specify a declarations array with the AppComponent as an item in that array. For NgModule, this means that the AppComponent belongs to that module. Lastly, we call the compileComponents() method and the setup is done. 

So what does the compileComponents() do? As per its name, it compiles components that are configured in the testing module. In the compilation process, it also inlines external CSS files as well as external templates. By calling compileComponents(), we also close down the possibility to further configure the testing module instance.

The second part of our test files are the tests. Look at the first test:

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

We see that we call TestBed.createComponent(AppComponent), this returns an object of type ComponentFixture<AppComponent>. We are able to interact with this object further by calling:

const app = fixture.debugElement.componentInstance;

This gives us a component instance, which is what we get when we instantiate an object from the following class:

@Component({})
export class AppComponent {
title: string;
}

The first test just wants to verify that we are able to create a component and the expect condition tests just that, that expect(app)  is truthy, meaning is it declared; and in truth it is.

For the second test, we actually try to investigate whether our component contains the properties and values we think; so the test looks like this:

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

Now, this test creates a component but it also calls fixture.detectChanges, which tells Angular to force change detection. This will make sure that the code in the constructor and any ngInit(), if it exists, is executed.

With a component specification we expect that the title property should get set when the component is created, like this:

@Component({})
export class AppComponent {
title: string = 'app works!'
}

That is exactly what the second test is testing:

expect(app.title).toEqual('app works!');

 Let's see how this works by extending our app.component.ts with one more field:

@Component({})
export class AppComponent {
title: string;
description: string;
constructor() {
this.title = 'app works'
this.description ='description';
}
}

We added the description field and also initialized it with a value; we will test whether this value is set to our property. Therefore, we need to add an extra expect condition in our test, so the test now looks like this:

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

As you can see, we have an extra expect condition and the test passes as it should. Don't take our word for it though; let's run our test runner using a node command. We do that by typing:

npm test

This will execute the test runner and it should look something like this:

This means that we understand how to extend our component and test for it. As a bonus, we now also know how to run our tests. Let's have a look at the third test. It is a bit different as it tests the template:

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

Instead of talking to fixture.debugElement.componentInstance, we now talk to fixture.debugElement.nativeElement. This will allow us to verify that the expected HTML markup is what we think it is. When we have access to the nativeElement, we can use the querySelector and find the elements we defined in our template and verify their content. 

We've gained quite a lot of insight just by looking at the test we were given. We now know the following:

  • We set up the test by calling TestBed.configureTestingModule() and pass it an object that looks like the object we pass to NgModule
  • We call TestBed.createComponent(<Component>) to get a reference to a component
  • We call debugElement.componentInstance on our component reference to get to the actual component and we can test for the existence and values of properties that should exist on our component object
  • We call debugElement.nativeElement to get a reference to the nativeElement and can now start verifying the resulting HTML
  • We also learned how to run our tests in the browser by typing npm test
fixture.debugElement.nativeElement points to the HTML element itself. When we use the querySelector() method, we are in fact using a method available in the Web API; it's not an Angular method.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset