Writing Unit Test Cases with Karma for Angular Components
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:
-
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.
-
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.
-
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.
Click the “Debug” button to initiate testing.
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.
Your first test case should pass! If another developer alters your code, your test will catch it:
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!
This example is simplified for demonstration purposes, but the key takeaway is that you should not shy away from writing unit tests.