Skip to main content

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.