Writing Unit Test Cases with Karma for Angular Components

August 07, 2020

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:

2020 08 07

  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.


Profile picture

Victor Leung, who blog about business, technology and personal development. Happy to connect on LinkedIn