Appearance
Tester un Observable utilisé dans un composant
Voici le code:
ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UsersComponent } from './users.component';
import { UserService } from 'src/app/core/services/user.service';
import { of } from 'rxjs';
describe('UsersComponent', () => {
let component: UsersComponent;
let fixture: ComponentFixture<UsersComponent>;
let userServiceStub: Partial<UserService>;
beforeEach(async () => {
userServiceStub = {
users$: of([{ id: 1, name: 'John', email: '[email protected]' }, { id: 2, name: 'Jane', email: '[email protected]' }]),
getAll: jasmine.createSpy('getAll').and.returnValue(of())
};
await TestBed.configureTestingModule({
declarations: [UsersComponent],
providers: [{ provide: UserService, useValue: userServiceStub }]
})
.compileComponents();
fixture = TestBed.createComponent(UsersComponent);
component = fixture.componentInstance;
});
it('should fetch users on initialization', () => {
component.ngOnInit();
expect(userServiceStub.getAll).toHaveBeenCalled();
});
it('should have users populated from observable', () => {
component.users$.subscribe(users => {
expect(users.length).toBe(2);
expect(users[0].name).toBe('John');
expect(users[1].name).toBe('Jane');
});
});
});
ts
import { Component, OnInit } from '@angular/core'
import { Observable } from 'rxjs';
import { UserService } from 'src/app/core/services/user.service';
import { User } from 'src/app/core/user';
@Component({
selector: 'app-users',
templateUrl: 'users.component.html'
})
export class UsersComponent implements OnInit {
users$: Observable<User[]> = this.userService.users$
constructor(private userService: UserService) { }
ngOnInit() {
this.userService.getAll().subscribe()
}
}
ts
import { HttpClient } from "@angular/common/http";
import { Injectable, inject } from "@angular/core";
import { BehaviorSubject, Observable, tap } from "rxjs";
import { User } from "../user.interface";
@Injectable({
providedIn: "root",
})
export class UserService {
private http = inject(HttpClient);
private _users$: BehaviorSubject<User[]> = new BehaviorSubject([] as User[]);
readonly url: string = "https://jsonplaceholder.typicode.com/users";
readonly users$: Observable<User[]> = this._users$.asObservable();
getAll(): Observable<User[]> {
return this.http.get<User[]>(this.url).pipe(
tap((users: User[]) => {
this._users$.next(users);
})
);
}
}
ts
export interface User {
id: number;
name: string;
username?: string;
email: string;
address?: {
street: string;
suite: string;
city: string;
zipcode: string;
geo: {
lat: string;
lng: string;
}
};
phone?: string;
website?: string;
company?: {
name: string;
catchPhrase: string;
bs: string;
};
}
Explication du test
Tester le retour de l'observable est essentiel pour s'assurer que le composant fonctionne comme prévu avec les données qu'il reçoit. Pour ce faire, nous allons ajuster le test précédent pour s'assurer que users$
est correctement alimenté par le service.
1. Le Stub de UserService
:
typescript
userServiceStub = {
users$: of([{ id: 1, name: 'John', email: '[email protected]' }, { id: 2, name: 'Jane', email: '[email protected]' }]),
getAll: jasmine.createSpy('getAll').and.returnValue(of())
};
Lorsque nous testons des composants dans Angular, nous souhaitons souvent les isoler de leurs dépendances externes pour ne tester que le composant lui-même. C'est ce qu'on appelle un test unitaire. UserService
est une dépendance externe pour UsersComponent
.
Le code ci-dessus crée un faux service, ou "stub", pour UserService
. Plutôt que d'utiliser le vrai service, qui pourrait faire des appels HTTP ou avoir d'autres comportements que nous ne souhaitons pas inclure dans notre test, nous utilisons ce stub.
users$
: C'est un Observable qui renvoie une liste d'utilisateurs. Dans un vrai scénario, cela pourrait être une liste récupérée d'une API. Mais pour notre test, nous utilisonsof()
derxjs
pour créer un Observable qui renvoie simplement une liste prédéfinie d'utilisateurs.getAll
: Dans le vraiUserService
, cette méthode pourrait faire un appel HTTP pour récupérer une liste d'utilisateurs. Ici, nous utilisonsjasmine.createSpy()
. Cela crée une fonction "espion" qui nous permet de suivre si elle a été appelée et avec quels arguments.and.returnValue(of())
indique que cette fonction espion doit simplement renvoyer un Observable vide lorsqu'elle est appelée.
2. Fournir le Stub à TestBed:
typescript
providers: [{ provide: UserService, useValue: userServiceStub }]
TestBed
est la principale API d'Angular pour tester des composants et d'autres éléments. Il nous permet de créer un environnement de test pour notre composant.
provide: UserService
: Cela indique à Angular que nous allons fournir quelque chose pour remplacer leUserService
normal dans ce contexte de test.useValue: userServiceStub
: C'est ici que nous disons à Angular d'utiliser notre faux service (stub) à la place du vraiUserService
. Ainsi, lorsque notre composant demande une instance deUserService
, il recevra notre stub à la place.
3. Tester l'Observable:
typescript
component.users$.subscribe(users => {
expect(users.length).toBe(2);
expect(users[0].name).toBe('John');
expect(users[1].name).toBe('Jane');
});
Le code ci-dessus teste le comportement de l'observable users$
dans notre composant.
component.users$.subscribe()
: Cela souscrit à l'observableusers$
. Lorsque cet observable émet des valeurs (c'est-à-dire envoie des données), la fonction à l'intérieur desubscribe()
est exécutée.