Angular Component Testing - The Complete Guide For Beginners


Want to learn how to test Angular components?

Here's your getting-started-guide that will teach you how to cleverly test Angular components.

How do you test Angular components?

What kind of tests do you need to write to make sure your Angular components always behave like a silly-belly show-monkey on slippery ice?

trick%20monkey

How do you write unit tests for your Angular components?

Say, how do you do Angular component testing?

In this complete guide for beginners you'll learn how to write your first Angular component test. Then we'll branch out into common component testing scenarios like testing component constructors as well as components with dependencies. Then we'll wrap things up with some pointers on the best practices when testing Angular components.

This article is full of many practical test examples that you can basically cut and paste into your own Angular project. I've done my best to make it so that you can just bookmark this page and come back to it any time you feel lost.

Table of Content

Core Concepts

If you just want to dive into the code and skip the theory then click here.

But before we throw some Angular tests together and think we're Angular testing experts we should cover some basic concepts that will help us write Angular component tests properly and effectively.

One of the important concepts we need to learn before testing an Angular component is the difference between a DOM test and a unit test.

Since an Angular component is made up of two main files - the Typescript file and the template file (HTML file) - there are two things we can test. First, we can test to make sure the component is displaying information properly. This is considered a DOM test. Second, we can test the component's Typescript file to make sure the "logic" of the component is behaving as we expected. Or, we could create tests that do both DOM and unit testing in one swoop.

In this article, we'll focus specifically on unit testing. But if you want to do DOM testing you can find a complete guide for beginner's here.

You've probably noticed the generic test that the Angular CLI generates when we create a new component?

It looks something like this.

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

import { MyComponent } from './my.component';

describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;

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

  beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

So what's going on in this basic Angular component test file?

Notice the two declared variables component and fixture? Those will be used to reference our component while we test it.

You'll also notice we have two beforeEach functions. One runs an asynchronous method for the asynchronous tests and the other one is not asynchronous.

In the beforeEach functions we're using Angular's TestBed function to set up a testing bed for our tests. TestBed is used to configure and initializes the needed environment for unit testing our components.

And last of all, we have a generic 'should create' test that makes sure our component is truthy. In other words, this simple test just checks to make sure our component is initialized and ready to use.

Writing your first Angular component test

Let's say we have a basic counter component like this. The idea is that when our add function gets called we'll update the total and then display that back to the user.

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

@Component({
  selector: 'app-counter',
  templateUrl: './counter.component.html',
  styleUrls: ['./counter.component.css']
})
export class CounterComponent implements OnInit {

  total: number = 0;

  constructor() { }

  ngOnInit(): void {
  }

  add(): void {
    this.total = this.total + 1;
  }

}

How do we test this component?

It's as simple as this.

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

import { CounterComponent } from './counter.component';

describe('CounterComponent', () => {
  let component: CounterComponent;
  let fixture: ComponentFixture<CounterComponent>;

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

  beforeEach(() => {
    fixture = TestBed.createComponent(CounterComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it("total should be one", () => {
    component.total = 0;
    component.add();
    expect(component.total).toEqual(1);
  });
});

Now, all you need to do is run ng test in the root directory of your Angular project and watch it run your tests.

Simple?

I know.

So how do we take it a bit further?

How do we test an Angular component that has a service dependency?

How to test an Angular component with service dependencies

Say that we move our counting stuff into a separate Angular service.

There are different ways to do this. We could inject the new CounterService directly into our test component but that's bad practice.

Instead of depending on the service, we should create a mock (also known as a stub). That way we can fake the dependency.

Here's how we do it.

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CounterService } from '../counter.service';

import { CounterComponent } from './counter.component';

describe('CounterComponent', () => {
  let component: CounterComponent;
  let fixture: ComponentFixture<CounterComponent>;
  let counterServiceStub: Partial<CounterService> = {
    add: () => null,
    get: () => 1,
  }

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ CounterComponent ],
      providers: [
        { provide: CounterService, useValue: counterServiceStub }
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(CounterComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it("total should be one", () => {
    component.total = 0;
    component.add();
    expect(component.total).toEqual(1);
  });
});

Notice how we declare a counterServiceStub and define how this mock service will behave?

And then we inject that into our TestBed configuration and use our mock service instead of the real thing.

But what about HTTP services?

Those are a bit tricker.

How to test an Angular component with HTTP service

So how do you test an Angular component that depends on Angular's HTTP client?

Let's say we add the HttpClient to our component to do some HTTP stuff. Like poking a counting service to see what chance we have of hitting the jackpot. 😋

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs';

@Component({
  selector: 'app-counter',
  templateUrl: './counter.component.html',
  styleUrls: ['./counter.component.css']
})
export class CounterComponent {

  constructor(private httpClient: HttpClient) { }

  add(): Observable<number> {
    return this.httpClient.post<number>("http://counting.service/api", { total: this.total });
  }

}

You'll notice our tests start giving a weird NullInjectionError - No provider for HttpClient!.

null%20injection%20error

Not a really good feeling, eh?

escape

How do we fix it?

import { HttpClient } from '@angular/common/http';
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { of } from 'rxjs';
import { CounterService } from '../counter.service';

import { CounterComponent } from './counter.component';

describe('CounterComponent', () => {
  let component: CounterComponent;
  let fixture: ComponentFixture<CounterComponent>;
  let httpClientSpy: { post: jasmine.Spy }

  beforeEach(async(() => {
    httpClientSpy = jasmine.createSpyObj('HttpClient', ['post']);

    TestBed.configureTestingModule({
      declarations: [ CounterComponent ],
      providers: [
        { provide: HttpClient, useValue: httpClientSpy }
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(CounterComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it("add should call remote api", fakeAsync((done: DoneFn) => {
    httpClientSpy.post.and.returnValue(of(1));

    component.add().subscribe(result => {
      expect(result).toEqual(1);
      expect(httpClientSpy.post).toHaveBeenCalled();
      done;
    });
  }));
});

Notice what we've just done?

We've created a spy, or in other words, we mocked the HttpClient. More specifically, we simulated the post function.

Then, we created a test to call the add function and made sure that it called our httpClientSpy.

If you're interested in more examples then check out how to test HTTP services.

Angular component testing best practices

When testing Angular components make sure you always mock service dependencies.

Always?

Well... if you're brave enough to evade the tornado storms that integration tests stir up then go ahead and inject the real services into your TestBed.

Otherwise, keep your tests simple and stable by always mocking service dependencies.

You can do this using the built-in Jasmine Spy's that come with the Angular framework. Or you can also check out the jasmin-auto-spies library that makes mocking effortless.

Also, keep in mind that components are a combination of an HTML template and a Typescript class, with the two of these working together to display information to the user.

Some argue that DOM testing is too hard or brittle while others say that it's absolutely necessary. So, make a wise choice and decide what's best for your situation. If unsure, I would recommend leaning toward more unit tests and fewer DOM tests.

signature

Angular Consultant

P.S. If you're one of those that likes to skip to the bottom (like me) then this article will...

  • Explain the difference between unit and DOM tests.
  • Show you how to write your first Angular component test.
  • Teach you how to test components that depend on other services.
  • Give you some of the best practices when testing Angular components.