Unit testing is an important part of Angular application development. Unit tests help ensure units of code work as expected in your Angular application. What are these units of code in the case of Angular? They’re the components, services, directives and pipes that make up the whole application.

The benefits of unit testing in Angular:

  • Catch errors early in the development process. You will likely discover issues with untested in later stages of testing. Unit testing forces you to consider use cases and fix them during development. 
  • Provides documentation for features to help the next engineer. Well written tests document your code and help others understand the intent. 
  • Creates a safety net for future as your application grows. Good tests ensure you don’t break a feature you implemented in the past.

Why bother with unit testing in Angular? 🤷🏻

Learning testing can be a bit tricky, but it’s definitely worth the effort. Even if it’s not immediately obvious, there are some good reasons why testing is important.

Testing helps ensure better code by discouraging the creation of “bad” code that’s hard to test. 

What should be unit tested in Angular? 🤔

Testing in Angular is about making sure each piece of your code does what it’s supposed to.

This means checking components, services, directives, pipes, and more. One by one, to ensure they work as intended. 

For components, it involves looking at how they react to user actions. Directives arse checked to see if they manipulate the webpage as expected. For services, tests ensure they carry out their tasks and call dependencies.

Testing also covers things like forms, error handling, interceptors, guards and API calls. 

Angular unit testing best practices 🚀

Pick the right framework

Should you use Jest or stick with default Karma setup?

Angular’s out of the box setup is using Jasmine & Karma for unit tests. Recently, the Angular team announced experimental Jest support as part of Angular V16. As Karma is getting deprecated soon, start using Jest to write unit tests in Angular.

Keep tests small and focused

Write small, focused tests that test one specific piece of functionality. Avoid writing monolithic tests that try to cover too much ground.

Test in isolation

Most of the time your components and services have service dependencies to get data. 

Use mocks and stubs to isolate components from its dependencies in unit tests. This ensures that your service tests focus on the service’s logic. 

You can use TestBed.inject to get services injected into your components during testing.

Testing in isolation will also force you to keep your components small. Avoid large, monolith components at all costs.

Minimal comments

Well written code is self explanatory.

Use describe blocks liberally for creating a good structure in spec files.

If you comment a section of code, consider extracting a method. 

Don’t rely on code coverage

Use code coverage to measure quality but don’t stress over 100% coverage. 

It can be misleading to only rely on lines covered, make sure to write a test for critical scenarios.

Simulate a future bug

Make sure tests are checking the right line of code. 

Try commenting out the code you are testing, if all tests are green, you are not testing the right thing.

Follow AAA pattern

Organise your tests into three sections.

  • Arrange (setup)
  • Act (perform the action or call the method)
  • Assert (verify the expected outcome)

Examples:

  it('should add two numbers', () => {
    // Arrange
    const a = 5;
    const b = 3;

    // Act
    const result = service.add(a, b);

    // Assert
    expect(result).toBe(8);
  });Code language: JavaScript (javascript)

Test components as a whole

You should test Angular components as a whole including HTML template. 

You want to ensure that the rendered HTML is correct based on the logic and data bindings. Interact with components via their template instead of calling class methods. TestBed provides a controlled environment for testing components. ComponentFixture helps access DOM elements and trigger change detection.

Imagine you have a counter component:

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

@Component({
  selector: 'app-counter',
  template: `
    <button (click)="increment()">Increment</button>
    <p>{{ counter }}</p>
  `,
})
export class CounterComponent {
  counter = 0;

  increment() {
    this.counter++;
  }
}Code language: TypeScript (typescript)

You can write a test to ensure the counter increments when the button is clicked:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CounterComponent } from './counter.component';
import { By } from '@angular/platform-browser';

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

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

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

  it('should increment the counter on button click', () => {
    // Arrange
    const button = fixture.debugElement.query(By.css('button'));
    const counterElement = fixture.debugElement.query(By.css('p'));

    // Act
    button.triggerEventHandler('click', null);
    fixture.detectChanges();

    // Assert
    expect(component.counter).toBe(1);
    expect(counterElement.nativeElement.textContent.trim()).toBe('1');
  });
});Code language: TypeScript (typescript)

If your components have input and output properties, make sure to test them. Input properties should be set and checked for correctness. Output properties should have their emitted values tested.

Follow Angular documentation to learn more about testing component scenarios.

Umut Esen

Software Engineer specialising in full-stack web application development.

Leave a Reply

This Post Has One Comment

  1. Angelique Prince

    I want to express my appreciation for the writer of this blog post. It’s clear they put a lot of effort and thought into their work, and it shows. From the informative content to the engaging writing style, I thoroughly enjoyed reading it.