Unit testing is a key aspect of modern web development, ensuring the individual components of your Angular application function as expected. In this guide, we'll explore the integrated testing environment provided by Jasmine and Karma, which are used in Angular applications to create and execute unit tests.
What are Jasmine and Karma?
-
Jasmine is a behavior-driven development framework that provides a syntax for writing tests. It includes features like spies, assertions, and test doubles to help verify that your code behaves as expected.
-
Karma is a test runner that allows you to execute JavaScript code in multiple real browsers, providing a robust testing environment. It enables you to run your Jasmine tests automatically or on a continuous integration pipeline.
Setting Up Your Angular Testing Environment
If you start with a standard Angular application created using the Angular CLI, Jasmine and Karma come preconfigured for you. However, if you need to set it up manually, follow these steps:
-
Install Angular CLI:
npm install -g @angular/cli
-
Create a New Angular Project:
ng new my-angular-app cd my-angular-app
-
Default Testing Dependencies: Angular CLI automatically installs Jasmine and Karma. You’ll find them in the
package.json
underdevDependencies
. -
Run Tests: Launch the test runner by executing:
ng test
It will start Karma and open your default browser, running the tests in real-time.
Writing Your First Unit Test
Let’s write a simple unit test for a component in Angular. Assume you have a component named CalculatorComponent
with a method add(a: number, b: number)
that sums two numbers.
calculator.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'app-calculator', template: `<div></div>`, }) export class CalculatorComponent { add(a: number, b: number): number { return a + b; } }
calculator.component.spec.ts
Create a new file named calculator.component.spec.ts
and add the following test:
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CalculatorComponent } from './calculator.component'; describe('CalculatorComponent', () => { let component: CalculatorComponent; let fixture: ComponentFixture<CalculatorComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [CalculatorComponent], }).compileComponents(); fixture = TestBed.createComponent(CalculatorComponent); component = fixture.componentInstance; }); it('should add two numbers correctly', () => { const result = component.add(2, 3); expect(result).toEqual(5); }); });
Breakdown of the Test
- describe: This function defines a test suite for
CalculatorComponent
. - beforeEach: Sets up the test environment before each test. It initializes the
CalculatorComponent
. - it: A single test case which checks whether the
add
method returns the expected result.
Utilizing Spies for Dependency Testing
One of Jasmine’s most powerful features is the ability to create spies, enabling you to track interactions with functions. Consider a scenario where CalculatorComponent
calls a service that logs operations.
Logging Service Example
export class LoggerService { log(message: string) { console.log(message); } }
Using the Logger in the Component
import { Component } from '@angular/core'; import { LoggerService } from './logger.service'; @Component({ selector: 'app-calculator', template: `<div></div>`, }) export class CalculatorComponent { constructor(private logger: LoggerService) {} add(a: number, b: number): number { const result = a + b; this.logger.log(`Added ${a} and ${b}: ${result}`); return result; } }
Spying on LoggerService
Here’s how you can create a spy for the LoggerService
in your tests:
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { CalculatorComponent } from './calculator.component'; import { LoggerService } from './logger.service'; describe('CalculatorComponent with Spies', () => { let component: CalculatorComponent; let fixture: ComponentFixture<CalculatorComponent>; let loggerServiceSpy: jasmine.SpyObj<LoggerService>; beforeEach(async () => { loggerServiceSpy = jasmine.createSpyObj('LoggerService', ['log']); await TestBed.configureTestingModule({ declarations: [CalculatorComponent], providers: [{ provide: LoggerService, useValue: loggerServiceSpy }], }).compileComponents(); fixture = TestBed.createComponent(CalculatorComponent); component = fixture.componentInstance; }); it('should log addition operation', () => { component.add(5, 3); expect(loggerServiceSpy.log).toHaveBeenCalledWith('Added 5 and 3: 8'); }); });
Understanding the Spy Code
- jasmine.createSpyObj: Creates a mock object for
LoggerService
with a methodlog
. - useValue: Provides the spy instance when
CalculatorComponent
is created. - toHaveBeenCalledWith: Asserts that the
log
method was called with the correct parameters.
Through these examples, we’ve built a solid foundation for unit testing in Angular applications. Remember, consistent unit testing increases code reliability and maintainability and is a vital practice for any Angular developer.