Lab 22: Unit Testing with Vitest
π Resourcesβ
π Starter Codeβ
album-wholesale-v17-signal-pre-jest
In this lab you will write unit tests for services and components using Vitest, Angular's recommended modern test runner. You will use vi.fn() for mocking, Vitest matchers, and lifecycle hooks.
π Instructionsβ
Step 1: Add Vitest to the Projectβ
Install compatible versions manually (ng add @angular/vitest installs Vitest 4.x which conflicts with the Angular 17 / Vite 5 toolchain):
npm install --save-dev vitest@2 @vitest/coverage-v8@2 @analogjs/vite-plugin-angular jsdom
Create vite.config.ts at the project root:
import { defineConfig } from 'vitest/config';
import angular from '@analogjs/vite-plugin-angular';
export default defineConfig({
plugins: [angular()],
test: {
globals: true,
environment: 'jsdom',
include: ['**/*.spec.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
},
},
});
Add a test script to package.json:
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
}
}
Step 2: Run the Existing Testsβ
npm test
Check that all existing tests pass before adding new ones.
Step 3: Test a Serviceβ
Create src/app/services/album.service.spec.ts:
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController }
from '@angular/common/http/testing';
import { AlbumService } from './album.service';
import { Album } from '../model/album.model';
describe('AlbumService', () => {
let service: AlbumService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [AlbumService],
});
service = TestBed.inject(AlbumService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => httpMock.verify());
it('should fetch albums', () => {
const mockAlbums: Album[] = [
{ id: 1, name: 'Test Album', artist: 'Artist', price: 9, description: '', tags: [] },
];
service.findAll().subscribe(albums => {
expect(albums).toHaveLength(1);
expect(albums[0].name).toBe('Test Album');
});
const req = httpMock.expectOne('http://localhost:3000/albums');
expect(req.request.method).toBe('GET');
req.flush(mockAlbums);
});
});
Step 4: Use vi.fn() to Mock a Dependencyβ
Create src/app/components/album-list/album-list.component.spec.ts:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { vi } from 'vitest';
import { of } from 'rxjs';
import { AlbumListComponent } from './album-list.component';
import { AlbumService } from '../../services/album.service';
describe('AlbumListComponent', () => {
let fixture: ComponentFixture<AlbumListComponent>;
let albumServiceMock: { findAll: ReturnType<typeof vi.fn> };
beforeEach(async () => {
albumServiceMock = {
findAll: vi.fn().mockReturnValue(of([
{ id: 1, name: 'Mock Album', artist: 'Mock Artist', price: 9, description: '', tags: [] },
])),
};
await TestBed.configureTestingModule({
imports: [AlbumListComponent],
providers: [
{ provide: AlbumService, useValue: albumServiceMock },
],
}).compileComponents();
fixture = TestBed.createComponent(AlbumListComponent);
fixture.detectChanges();
});
it('should display albums', () => {
const items = fixture.nativeElement.querySelectorAll('.album-card');
expect(items.length).toBeGreaterThan(0);
});
it('should call findAll on init', () => {
expect(albumServiceMock.findAll).toHaveBeenCalledOnce();
});
});
Step 5: Use Lifecycle Hooksβ
Vitest provides beforeAll, beforeEach, afterEach, afterAll:
describe('Cart calculations', () => {
let items: CartItem[];
beforeEach(() => {
items = [
{ id: 1, quantity: 2, price: 10 },
{ id: 2, quantity: 1, price: 20 },
];
});
it('should calculate total', () => {
const total = items.reduce((sum, i) => sum + i.quantity * i.price, 0);
expect(total).toBe(40);
});
it('should count items', () => {
expect(items).toHaveLength(2);
});
});
Step 6: Run with Coverageβ
npx vitest --coverage
Check the coverage report and ensure critical service methods are covered.
Step 7: Open the UIβ
npx vitest --ui
Explore the interactive test browser to navigate tests and see results in real time.