I’d like to make the case for writing unit tests for your Angular web app. Accelerating time to production is not a valid excuse for accumulating technical debt. Here are some compelling reasons to start:

  1. Unit tests help identify issues as early as possible, especially when multiple teams are working on the same codebase, inadvertently introducing bugs. Avoiding middle-of-the-night calls for production support is a worthy goal.

  2. Tests enable you to refactor your code confidently, ensuring that your app continues to function as expected. You can divide your code into manageable, testable units, as opposed to dealing with a monolithic system.

  3. Your company’s policy may mandate a certain level of code coverage, often 80% or higher.

If you’re new to this, you may not know how to get started or why it’s important. Fortunately, Angular makes it easy. To begin, simply run the following command in your project directory:

npm run test

This will open a Chrome browser window at localhost, on port 9876.

Image 1

Click the “Debug” button to initiate testing.

Image 2

At this point, no tests will run because we haven’t written any yet. But you can start writing test cases to cover specific, isolated pieces of code. For instance, let’s consider a login.component.ts file, which contains a login() method that toggles a boolean flag from false to true:

export class LoginComponent {
  isLogon = false

  login() {
    this.isLogon = true
  }
}

Next, create a file named login.component.spec.ts for your test cases. Write your first test case as follows:

import { async, ComponentFixture, TestBed } from "@angular/core/testing"
import { LoginComponent } from "./login.component"

describe("LoginComponent", () => {
  let component: LoginComponent
  let fixture: ComponentFixture<LoginComponent>

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

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

  it("should be able to log on", () => {
    component.login()
    expect(component.isLogon).toBeTruthy()
  })
})

Inside your describe() function, you’ll find the test cases. Each case is within its own it() function. The aim here is to test if the isLogon flag turns true after the login() method is triggered.

Image 3

Your first test case should pass! If another developer alters your code, your test will catch it:

Image 4

In a real-world scenario, you might make an API call to a server. However, it’s crucial not to call the actual API during your test. Instead, you should mock your API call with stub data.

For instance, let’s enhance our LoginComponent to make a service call:

import { AuthenticationService } from "../../services/authentication.service"

export class LoginComponent {
  constructor(private authenticationService: AuthenticationService) {}

  isLogon = false

  login() {
    this.authenticationService.login().subscribe(
      data => {
        this.isLogon = true
      },
      error => {
        this.isLogon = false
      }
    )
  }
}

Now your test will fail because the AuthenticationService isn’t yet injected into our testing environment. We can fix this as shown below:

import { async, ComponentFixture, TestBed } from "@angular/core/testing"
import { LoginComponent } from "./login.component"
import { AuthenticationService } from "../../services/authentication.service"
import { of } from "rxjs"

const stubData = {
  username: "testing",
}

class FakeAuthenticationService {
  login() {
    return of(stubData)
  }
}

describe("LoginComponent", () => {
  let component: LoginComponent
  let fixture: ComponentFixture<LoginComponent>
  const newFakeAuthenticationService = new FakeAuthenticationService()

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [LoginComponent],
      providers: [
        {
          provide: AuthenticationService,
          useValue: newFakeAuthenticationService,
        },
      ],
    }).compileComponents()
  }))

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

  it("should be able to log on", () => {
    component.login()
    expect(component.isLogon).toBeTruthy()
  })
})

Your test case should now pass!

Image 5

This example is simplified for demonstration purposes, but the key takeaway is that you should not shy away from writing unit tests.