Setup testing Angular avec Vite, Jasmine et Karma : mocking services, testing HTTP, bonnes pratiques et couverture de code.
Introduction
Les tests unitaires sont essentiels pour une application fiable et maintenable. Angular fournit une excellente pile de testing avec Jasmine (framework de test) et Karma (test runner). Cette combinaison permet de tester les composants, services et directives en isolement.
Configuration initiale
Angular CLI configure automatiquement Jasmine + Karma lors de la création du projet :
// Générer un composant avec son fichier de test
ng generate component my-component
// Lancer les tests en watch mode
ng test
// Lancer les tests une seule fois (CI/CD)
ng test --no-watch --code-coverage
Écrire votre premier test
Chaque test démarre par describe() qui groupe les tests d'une classe. Puis beforeEach() initialise TestBed (le contexte d'injection d'Angular) :
// my-component.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyComponent } from './my-component.component';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
beforeEach(async () => {
// Configurer le module de test avec les dépendances
await TestBed.configureTestingModule({
declarations: [MyComponent]
}).compileComponents();
// Créer une instance du composant
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
// Déclencher la détection de changements (ngOnInit, etc.)
fixture.detectChanges();
});
it('should create the component', () => {
expect(component).toBeTruthy();
});
it('should display title in template', () => {
const titleElement = fixture.nativeElement.querySelector('h1');
expect(titleElement.textContent).toBe('Mon Titre');
});
it('should update counter when button clicked', () => {
const button = fixture.nativeElement.querySelector('button');
expect(component.counter).toBe(0);
button.click();
fixture.detectChanges();
expect(component.counter).toBe(1);
});
});
TestBed.configureTestingModule()crée un module isolé pour le testfixture.detectChanges()force Angular à exécuter la détection de changementsexpect().toBe()est une assertion Jasmine- Testez le DOM via
fixture.nativeElementoufixture.debugElement
Mocking des services
Isolez vos composants en mockant les services injectés. Ne testez jamais l'API réelle dans les tests unitaires :
// Créer un mock service
const mockUserService = {
getUsers: jasmine.createSpy('getUsers')
.and.returnValue(of([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]))
};
// Fournir le mock au TestBed
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [UserListComponent],
providers: [
// Remplacer le vrai UserService par le mock
{ provide: UserService, useValue: mockUserService }
]
}).compileComponents();
fixture = TestBed.createComponent(UserListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// Tester l'interaction avec le mock
it('should load users on init', () => {
expect(mockUserService.getUsers).toHaveBeenCalled();
expect(component.users.length).toBe(2);
});
it('should display user names', () => {
const names = fixture.nativeElement.querySelectorAll('.user-name');
expect(names[0].textContent).toContain('Alice');
expect(names[1].textContent).toContain('Bob');
});
Tests des appels HTTP
Utilisez HttpTestingController pour intercepter et valider les requêtes HTTP sans les envoyer réellement :
// Importer HttpClientTestingModule
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
declarations: [UserListComponent],
providers: [UserService]
}).compileComponents();
fixture = TestBed.createComponent(UserListComponent);
userService = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
});
// Tester l'appel HTTP
it('should fetch users from API', () => {
const mockUsers = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
userService.getUsers().subscribe(users => {
expect(users.length).toBe(2);
expect(users[0].name).toBe('Alice');
});
// Intercepter la requête GET /api/users
const req = httpMock.expectOne('/api/users');
expect(req.request.method).toBe('GET');
// Simuler la réponse du serveur
req.flush(mockUsers);
});
// Vérifier qu'aucune requête n'a été envoyée
afterEach(() => {
httpMock.verify(); // Lance une erreur si des requêtes non consommées subsistent
});
httpMock.expectOne(url)pour une seule requêtehttpMock.match()pour plusieurs requêtes à la même URLreq.error()pour simuler une erreur 500httpMock.verify()en cleanup pour attraper les requêtes orphelines
Conclusion
Les tests unitaires sont un investissement dans la stabilité de votre code. Avec Jasmine et Karma (ou Vitest), vous pouvez tester chaque couche de votre application Angular : composants, services, directives, pipes.
- Visez 80%+ de couverture de code (code coverage)
- Testez les cas nominaux ET les cas d'erreur
- Utilisez Spectator ou Testing Library pour des tests plus lisibles
- Intégrez les tests dans votre CI/CD (GitLab CI, GitHub Actions, etc.)
- Considérez Vitest pour une exécution plus rapide (Angular 17+)