Tester un composant
Lorsqu'on teste un composant, on voudra également s'assurer de simuler les services. Si on utilise seulement une partie des méthodes d'un service dans un composant il peut être pratique de créer un service partiel qui remplacera le vrai service.
let authServicePartiel: Partial<AuthService>;
const fausseCreationCompte = async ({courriel, mdp, nom}): Promise<void> => {};
authServicePartiel = {
creerCompte: fausseCreationCompte
};
Pour injecter les dépendances : même principe que pour les dépendances des services.
Pour obtenir une référence au service injecté (si on doit l'utiliser dans un test par exemple) : passer par le composant de test plutôt que directement par TestBed.
// Extrait de tests d'une page d'inscription
TestBed.configureTestingModule({
declarations: [ InscriptionPage ],
imports: [FormsModule, ReactiveFormsModule],
providers: [...]
}).compileComponents();
fixture = TestBed.createComponent(InscriptionPage);
// Obtenir le service pour les tests
authService = fixture.debugElement.injector.get(AuthService);
component = fixture.componentInstance;
fixture.detectChanges();
Utiliser les espions
On peut créer les espions et les ajouter dans les providers comme pour les services.
// Extrait du test de adoption-chat.ts (components/adoption-chat.spec.ts)
beforeEach(async () => {
adoptionServiceSpy = jasmine.createSpyObj("AdoptionService", ['listeChatsAdoptables']);
await TestBed.configureTestingModule({
imports: [AdoptionChat],
providers: [
{provide: AdoptionService, useValue: adoptionServiceSpy},
]
})
.compileComponents();
fixture = TestBed.createComponent(AdoptionChat);
component = fixture.componentInstance;
fixture.detectChanges();
});
Tester le contenu d'une page
Pour tester le contenu, on obtiendra la page par nativeElement de debugElement
it("Le titre de la composante devrait contenir le texte Inscription", () => {
const inscriptionElement: HTMLElement = fixture.debugElement.nativeElement;
const title = inscriptionElement.querySelector("h1").textContent;
expect(title).toContain("Inscription", `Titre actuel: «${title}»`);
});
Tester des formulaires réactifs
- Importer les modules FormsModule, ReactiveFormsModule (section imports)
- Pour accéder à un champ (control) : component.formInscription.controls.nomChamp
- Par défaut le formulaire sera vide
- Pour ajouter des valeurs dans le formulaire: champ.setValue("valeur") où champ est
component.formInscription.controls.nomChamp - Pour savoir si un champ ou le formulaire est valide : .valid
- Pour savoir si le champ a levé une erreur :
.hasError("nomErreur")où les erreurs correspondent auxValidators
Exemples
adoption-chat.spec.ts
import {Component, inject} from '@angular/core';
import {Chat} from '../../interfaces/chat';
import {AdoptionService} from '../../services/adoption-service';
@Component({
selector: 'app-adoption-chat',
imports: [],
templateUrl: './adoption-chat.html',
styleUrl: './adoption-chat.css'
})
export class AdoptionChat {
adoptionService: AdoptionService = inject(AdoptionService);
chats: Chat[] = [];
ngOnInit () {
this.chats = this.adoptionService.listeChatsAdoptables();
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdoptionChat } from './adoption-chat';
import {AdoptionService} from '../../services/adoption-service';
import {Chat} from '../../interfaces/chat';
describe('AdoptionChat', () => {
let component: AdoptionChat;
let fixture: ComponentFixture<AdoptionChat>;
let adoptionServiceSpy: jasmine.SpyObj<AdoptionService>;
const mockChats: Chat[] = [
{ nom: 'Moustache', couleur: 'gris', age: 2 },
{ nom: 'Minette', couleur: 'blanc', age: 3 },
];
beforeEach(async () => {
adoptionServiceSpy = jasmine.createSpyObj("AdoptionService", ['listeChatsAdoptables']);
await TestBed.configureTestingModule({
imports: [AdoptionChat],
providers: [
{provide: AdoptionService, useValue: adoptionServiceSpy},
]
})
.compileComponents();
fixture = TestBed.createComponent(AdoptionChat);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it("listeChatsAdoptables devrait être appelée", () => {
adoptionServiceSpy.listeChatsAdoptables.and.returnValue(mockChats);
expect(adoptionServiceSpy.listeChatsAdoptables).toHaveBeenCalled();
})
it("Les chats disponibles pour adoption devraient apparaître", () => {
// Simuler l'appel
adoptionServiceSpy.listeChatsAdoptables.and.returnValue(mockChats);
// Lancer ngOnInit pour effectuer l'appel au service
component.ngOnInit();
// Mettre à jour le component
fixture.detectChanges();
const chatElement = fixture.debugElement.nativeElement;
const chats = chatElement.querySelectorAll('li');
expect(chats.length).toBe(2);
});
});
Dans l'exemple précédent, les aspects suivants sont testés
- Le composant est bien créé
- La méthode
listeChatsAdoptablesest bien appelée - Il y a bien un élément de liste pour chaque chat adoptable
Tester le routage dans le composant
Pour tester que le routage se fait bien dans le composant, on pourra utiliser des espions également.
On pourra ensuite tester le fonctionnement avec des méthodes de Jasmine
expect(router.navigateByUrl).toHaveBeenCalledTimes(1);
expect(router.navigateByUrl).toHaveBeenCalledWith("/connexion");
Avec des observables
La marche à suivre est similaire lorsqu'on travaille avec des observables dans les components.
- Au lieu d'utiliser
returnValue()on utiliserareturnValue(of())pour lorsqu'il s'agit d'observables.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ListeFilms } from './liste-films';
import {FilmService} from '../../services/film-service';
import {of} from 'rxjs';
import {mockFilmsList} from '../../mocks/mock-films';
describe('ListeFilms', () => {
let component: ListeFilms;
let fixture: ComponentFixture<ListeFilms>;
let filmServiceEspion: jasmine.SpyObj<FilmService>;
beforeEach(async () => {
filmServiceEspion = jasmine.createSpyObj('FilmService', ['getAllFilms']);
await TestBed.configureTestingModule({
imports: [ListeFilms],
providers: [
{provide: FilmService, useValue: filmServiceEspion},
]
})
.compileComponents();
fixture = TestBed.createComponent(ListeFilms);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('getAllFilms devrait être appelé', () => {
filmServiceEspion.getAllFilms.and.returnValue(of(mockFilmsList))
component.ngOnInit();
fixture.detectChanges();
expect(filmServiceEspion.getAllFilms).toHaveBeenCalled();
});
});
Attention à l'opértaeur pipe
Si votre composant traite des données reçues dans ngOnInit() avec un pipe l'approche démontrée ci-dessus ne fonctionnera pas et vous recevrez une erreur indiquant TypeError: Cannot read properties of undefined (reading 'pipe').
Lorsque fixture.detectChanges() est appellé dans le bloc beforeEach(), un premier appel à ngOnInit() est fait. Puisqu'à ce moment, l'espion n'a pas été appellé (filmServiceEspion.getAllFilms.and.returnValue(of(mockFilmsList))), aucun observable n'a été retourné et donc l'opérateur pipe échoue.
Pour résoudre le problème, on doit appeller nos espions dans le bloc beforeEach() juste avant l'appel à fixture.detectChanges(). Le test précédent deviendrait donc:
describe('ListeFilms', () => {
let component: ListeFilms;
let fixture: ComponentFixture<ListeFilms>;
let filmServiceEspion: jasmine.SpyObj<FilmService>;
beforeEach(async () => {
filmServiceEspion = jasmine.createSpyObj('FilmService', ['getAllFilms']);
await TestBed.configureTestingModule({
imports: [ListeFilms],
providers: [
{provide: FilmService, useValue: filmServiceEspion},
]
})
.compileComponents();
fixture = TestBed.createComponent(ListeFilms);
component = fixture.componentInstance;
filmServiceEspion.getAllFilms.and.returnValue(of(mockFilmsList));
fixture.detectChanges();
});
it('getAllFilms devrait être appelé', () => {
expect(filmServiceEspion.getAllFilms).toHaveBeenCalled();
});
}