Tests Unitaires
Running Tests (Vitest)
Section intitulée « Running Tests (Vitest) »# Run tests oncenpm run test
# Watch modenpm run test:watch
# Run with coveragenpm run test:coverage
# Run specific test filenpm run test album.component.spec.ts
# UI modenpm run test:uiConfiguration Vitest
Section intitulée « Configuration Vitest »Install Vitest and Angular testing utilities:
npm install -D vitest @vitest/ui @angular/platform-browser-dynamicnpm install -D @analogjs/vite-plugin-angularConfigure vite.config.ts:
import { defineConfig } from 'vitest/config';import angular from '@analogjs/vite-plugin-angular';
export default defineConfig({ plugins: [angular()], test: { globals: true, environment: 'jsdom', setupFiles: ['src/test-setup.ts'], include: ['**/*.spec.ts'], coverage: { provider: 'v8', reporter: ['text', 'html', 'lcov'] } }});Component Basique Test
Section intitulée « Component Basique Test »Component:
import { Component, input, output } from '@angular/core';
@Component({ selector: 'app-album', standalone: true, template: ` <h1>{{ title() }}</h1> <button (click)="play()">Play</button> `})export class AlbumComponent { title = input<string>(''); played = output<void>();
play() { this.played.emit(); }}Test:
import { ComponentFixture, TestBed } from '@angular/core/testing';import { describe, it, expect, beforeEach, vi } from 'vitest';import { AlbumComponent } from './album.component';
describe('AlbumComponent', () => { let component: AlbumComponent; let fixture: ComponentFixture<AlbumComponent>;
beforeEach(async () => { await TestBed.configureTestingModule({ imports: [AlbumComponent] }).compileComponents();
fixture = TestBed.createComponent(AlbumComponent); component = fixture.componentInstance; });
it('should create', () => { expect(component).toBeTruthy(); });
it('should display title', () => { fixture.componentRef.setInput('title', 'Dark Side'); fixture.detectChanges();
const h1 = fixture.nativeElement.querySelector('h1'); expect(h1.textContent).toBe('Dark Side'); });
it('should emit on button click', () => { const playSpy = vi.fn(); component.played.subscribe(playSpy);
const button = fixture.nativeElement.querySelector('button'); button.click();
expect(playSpy).toHaveBeenCalled(); });});Tests Services
Section intitulée « Tests Services »Service:
@Injectable({ providedIn: 'root' })export class AlbumService { private http = inject(HttpClient);
getAlbums(): Observable<Album[]> { return this.http.get<Album[]>('/api/albums'); }}Test:
import { TestBed } from '@angular/core/testing';import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';import { describe, it, expect, beforeEach, afterEach } from 'vitest';import { AlbumService } from './album.service';
describe('AlbumService', () => { let service: AlbumService; let httpMock: HttpTestingController;
beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule] });
service = TestBed.inject(AlbumService); httpMock = TestBed.inject(HttpTestingController); });
afterEach(() => { httpMock.verify(); });
it('should get albums', () => { const mockAlbums = [ { id: 1, name: 'Abbey Road', artist: 'The Beatles' } ];
service.getAlbums().subscribe(albums => { expect(albums).toEqual(mockAlbums); });
const req = httpMock.expectOne('/api/albums'); expect(req.request.method).toBe('GET'); req.flush(mockAlbums); });});Tests with Mocks
Section intitulée « Tests with Mocks »import { describe, it, expect, vi } from 'vitest';
describe('AlbumListComponent', () => { it('should load albums', () => { const mockService = { getAlbums: vi.fn().mockReturnValue(of([{ id: 1, name: 'Album' }])) };
TestBed.configureTestingModule({ imports: [AlbumListComponent], providers: [ { provide: AlbumService, useValue: mockService } ] });
const fixture = TestBed.createComponent(AlbumListComponent); fixture.detectChanges();
expect(mockService.getAlbums).toHaveBeenCalled(); expect(fixture.componentInstance.albums).toHaveLength(1); });});Tests Async Code
Section intitulée « Tests Async Code »import { describe, it, expect, vi } from 'vitest';import { fakeAsync, tick } from '@angular/core/testing';
it('should debounce search', fakeAsync(() => { component.search('test');
tick(299); expect(searchSpy).not.toHaveBeenCalled();
tick(1); // 300ms autal expect(searchSpy).toHaveBeenCalledWith('test');}));Vitest Matchers
Section intitulée « Vitest Matchers »Common assertions:
// Basicexpect(value).toBe(expected);expect(value).toEqual(expected);expect(value).toBeTruthy();expect(value).toBeFalsy();expect(value).toBeNull();expect(value).toBeUndefined();
// Numbersexpect(value).toBeGreaterThan(3);expect(value).toBeLessThan(5);
// Stringsexpect(text).toContain('substring');expect(text).toMatch(/pattern/);
// Arraysexpect(array).toHaveLength(3);expect(array).toContain(item);
// Functionsexpect(fn).toHaveBeenCalled();expect(fn).toHaveBeenCalledWith(arg1, arg2);expect(fn).toHaveBeenCalledTimes(2);Bonnes Pratiques
Section intitulée « Bonnes Pratiques »- ✅ Utiliser
describeà group related tests - ✅ Utiliser
beforeEachfor common setup - ✅ Test behavior, not implementation
- ✅ Mock external dependencies
- ✅ Utiliser descriptive test names
- ✅ Aim for 80%+ coverage
- ✅ Test edge cases (null, empty, errors)

