icon-search
silvio-kundt-65518-unsplash

Introduction to Unit Testing Angular 2 Components

Jakub Majerz 03.01.2017

We have already mentioned that writing automated tests for your application is important. And it doesn’t seem to be an extravagant stance on the subject as the Internet is full of similar statements. In this article, we start our adventure with unit testing Angular 2 Components. Unit tests (A.K.A. component tests – don’t confuse with Angular 2 Components!) are a great way to both boost your confidence in the correctness of the application as well as catch any mistakes and regressions early on, as they try to creep into your code.

A simple unit test

Suppose we have an extremely simple class running in the wild somewhere:

export class OddOrEven {

  public isNumberEven(num: number): boolean {
    return num % 2 === 0;
  }

  public isNumberOdd(num: number): boolean {
    return !this.isNumberEven(num);
  }
}

It has no dependencies and does not rely on any framework’s “magic” in order to function properly. This is why unit testing this class is pretty straightforward:

describe("OddOrEven", () => {

  it("should correctly classify numbers as odd", () => {
    const oddOrEven = new OddOrEven();
    expect(oddOrEven.isNumberOdd(1)).toBe(true);
    expect(oddOrEven.isNumberOdd(2)).toBe(false);
  });  

});

Remark: We’ll be using Jasmine (2.5) in our unit tests without explaining its basic features. If you’re looking for a place to start your adventure with Jasmine, its official page seems to be a good one.

If this class doesn’t look like an Angular 2 service (primitive, but still) to you, imagine adding @Injectable() decorator above the class’ name and renaming it to OddOrEvenService. Voilà! If this still doesn’t ring a bell, consider having a look at our article on Angular 2 services. The point is that if we have services without external dependencies, unit testing them does not even require knowing they belong to an Angular application.

The need for testing utilities

Now, things get a bit more complicated when a class makes little sense on its own and is interwoven with framework mechanisms, as is the case with Angular 2 Components. They rely on the framework’s change detection mechanism, the way data binding works and so on. If we extracted such a component class from the app with all its decorators, ngOnInits, etc., and plugged it directly into a describe() block just like the one above, we’d be straight on a path to disaster. It would simply explode in our face. This is because testing frameworks, such as Jasmine, can’t know and emulate all of Angular’s intricacies. This is why the Angular team included a set of utilities to facilitate unit testing your application.

Main Angular 2 unit testing utilities

Let’s start our component unit testing adventure with something simple:

@Component({
  selector: 'text-display',
  template: `
    <h2>{{text}}</h2>
  `
})
export class TextDisplayComponent {
  text = 'sample text';
}

Even though this component is trivial, suppose it somehow found its way into a real Angular 2 application. In order to do so it would first need to be declared in some module. And this is the first thing we need to do when unit testing a component – provide a module in which our component will be used. We do this by using a class named TestBed, which is used to set up our testing environment. We use its static configureTestingModule() method where we pass an object similar to the one in a traditional @NgModule() decorator:

TestBed.configureTestingModule({
  // We declare only our TextDisplayComponent
  declarations: [TextDisplayComponent]
});

Then we use its another method – createComponent() – to create a ComponentFixture which is a test environment around the component. Using the freshly created fixture, we can access the component instance itself, via the .componentInstance field:

let textDisplayComponent: TextDisplayComponent;
let textDisplayComponentFixture: ComponentFixture<TextDisplayComponent>;
  
// Use TestBed to create ComponentFixture for our TextDisplayComponent:
textDisplayComponentFixture = TestBed.createComponent(TextDisplayComponent);
// Access TextDisplayComponent instance:
textDisplayComponent = textDisplayComponentFixture.componentInstance;

Just as ComponentFixture is a handle around the test environment of the component, the component’s DOM element also has its own handle – represented by a DebugElement class. We can access it via .debugElement field of the component fixture. This debugElement allows us to access the native HTML element (via the .nativeElement). We can also use its .query() method to find other elements inside it (it also returns a DebugElement object):

let textDebugElement: DebugElement;
let textElement: HTMLElement;

// We need to pass a Predicate to .query() - in our case it's By.css() where we pass a CSS selector.
textDebugElement = textDisplayComponentFixture.debugElement.query(By.css("h2"));
// This is our <h2> element that contains the displayed text:
textElement = textDebugElement.nativeElement;
});

There’s just one more thing before we can unit test our component – the change detection. In production, it is handled by Angular automatically. But when unit testing, we need to manually “tell” the ComponentFixture to run change detection on our component by calling detectChanges(). This is to allow a more fine-grained control in tests.

It appears convoluted at first but it seems that it’s the price to pay for a suite of test utilities.

The complete example

Let’s see how it all works out in action:

import {ComponentFixture, TestBed} from '@angular/core/testing';
import {By} from "@angular/platform-browser";
import {DebugElement} from "@angular/core";

describe("TextDisplayComponent", () => {

  let textDisplayComponent: TextDisplayComponent;
  let textDisplayComponentFixture: ComponentFixture<TextDisplayComponent>;
  let textDebugElement: DebugElement;
  let textElement: HTMLElement;

  beforeEach(() => {

    // Use TestBed to configure module for the tests below
    TestBed.configureTestingModule({
      // We declare only our TextDisplayComponent
      declarations: [TextDisplayComponent]
    });

    // Use TestBed to create ComponentFixture for our TextDisplayComponent:
    textDisplayComponentFixture = TestBed.createComponent(TextDisplayComponent);
    // Access TextDisplayComponent instance:
    textDisplayComponent = textDisplayComponentFixture.componentInstance;

    // We need to pass a Predicate to .query() - in our case it's By.css() where we pass a CSS selector.
    textDebugElement = textDisplayComponentFixture.debugElement.query(By.css("h2"));
    // This is our <h2> element that contains the displayed text:
    textElement = textDebugElement.nativeElement;
  });

  it("should display text based on its 'text' field", () => {
    // We need to call detectChanges() - otherwise the value on the component will not
    // make it to the template. The next test demonstrates that.
    textDisplayComponentFixture.detectChanges();
    expect(textElement.textContent).toContain(textDisplayComponent.text);
  });

  it("shows that angular does not run change detection after TestBed.createComponent()", () => {
    expect(textElement.textContent).toBe("");
  });

  // If we change a component field, we need detectChanges() in order to propagate these changes
  it("should display a different test text after we change it and run change detection", () => {
    textDisplayComponent.text = "Test Title";
    textDisplayComponentFixture.detectChanges();
    expect(textElement.textContent).toContain("Test Title");
  });
});

This code demonstrates the use of the bread and butter utilities we described above. We have three test suites, verifying proper text display and showing that we need to manually run change detection on our components.

Templates in external HTML files

In the example above, TextDisplayComponent‘s template is declared inline – inside the @Component() decorator. In a real app, it’s likely that each component would have its template in a separate HTML file. Provided, of course, that its template is sufficiently large – keeping one-line template in a separate file usually makes little sense. This introduces some complications as TestBed.createComponent() method is synchronous and the Angular template compiler reads template files from the file system asynchronously. This mismatch in synchronicity brings us to another unit-test utility method: async(). By wrapping the function passed to beforeEach() in an async() call and chaining TestBed.configureTestingModule() with compileComponents() the problem is solved. So if TextDisplayComponent had its template in a separate file, our beforeEach() call would look as follows:

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

As far as the rest of the “old” beforeEach() is concerned, we have two options:

  • chain .then() to compileComponents() and place the remaining code there or
  • have the remaining code in a separate, synchronous beforeEach() – the test runner will wait for the first asynchronous beforeEach() to finish before calling the second one.

Mocking service dependencies

Suppose we’ve got the following component:

@Component({
  selector: 'users-list',
  template: `
    <h2>Users</h2>
    <div *ngFor="let user of users | async"
         [ngClass]="{'vip-user': user.isVip}">
      {{user.id}} -- {{user.name}}
    </div>
  `
})
export class UsersListComponent {
  public users: Observable<User[]>;

  constructor(private usersService: UsersService) {
    this.users = this.usersService.getUsers();
  }
}

This component’s only responsibility is to display a list of users and somehow highlight the VIP ones. We can safely assume that the UserService makes an HTTP request to get the users from the server. When unit testing the component itself, we only really care about whether it can properly display both kinds of users. To verify that, we only need two users: one with .isVip === true and another with .isVip === false. What is more, we do not care if the UserService has any other methods. This is why we’ll use a mock instead of the real service. Thus our unit test could look like this:

// This will replace the "genuine" UsersService during test:
class UsersServiceMock {
  public getUsers(): Observable<User[]> {
    const users: User[] = new Array<User>(
      new User("1", "John Smith", true),
      new User("2", "Ann Greene", false)
    );

    return Observable.of(users);
  }
}

describe("UsersListComponent", () => {

  let fixture: ComponentFixture<UsersListComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [UsersListComponent],
      // Here we swap the mocked version:
      providers: [{provide: UsersService, useClass: UsersServiceMock}]
    });

    fixture = TestBed.createComponent(UsersListComponent);
  });

  it("should correctly display users based on their type", () => {
    fixture.detectChanges();

    const usersDebugElems = fixture.debugElement.queryAll(By.css("div"));
    const vipUserElem = usersDebugElems[0].nativeElement;
    const normalUserElem = usersDebugElems[1].nativeElement;

    expect(vipUserElem.classList.contains("vip-user")).toBeTruthy();
    expect(normalUserElem.classList.contains("vip-user")).toBeFalsy();
  });

});

Our mocked service implementation is pretty straightforward: it returns two users, of each type we care about. It does nothing more. Since Angular http service returns Observables, we’re doing this as well.

Note the use of queryAll() – it is similar to query() that we used previously, but instead of one DebugElement, it returns all of them that satisfy the predicate.

Conclusion

We have only scratched the surface of unit testing Angular 2 components. What we’ve covered allows us to start writing some serious unit tests and gives us solid foundations for further exploration. But there’s still plenty to come in the next articles on unit testing Angular 2 applications. So if you’d like to improve the quality of your application and learn more on the subject – be on the lookout for future articles!

comments: 0