Lab 22: Unit Testing with Vitest
📖 Resources
Section titled “📖 Resources”🚀 Starter Code
Section titled “🚀 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
Section titled “📝 Instructions”Step 1: Add Vitest to the Project
Section titled “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 jsdomCreate 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
Section titled “Step 2: Run the Existing Tests”npm testCheck that all existing tests pass before adding new ones.
Step 3: Test a Service
Section titled “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
Section titled “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
Section titled “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
Section titled “Step 6: Run with Coverage”npx vitest --coverageCheck the coverage report and ensure critical service methods are covered.
Step 7: Open the UI
Section titled “Step 7: Open the UI”npx vitest --uiExplore the interactive test browser to navigate tests and see results in real time.

