En aquest tema, aprendrem com simular dependències en les proves d'Angular. La simulació de dependències és una tècnica essencial per aïllar el codi que estem provant i assegurar-nos que les proves són fiables i repetibles. Això és especialment important quan les dependències poden tenir efectes secundaris, com ara fer sol·licituds HTTP o interactuar amb serveis externs.
Objectius del tema
- Comprendre què és la simulació de dependències.
- Aprendre a utilitzar
Jasmineper simular dependències. - Veure exemples pràctics de simulació de serveis en proves unitàries.
Què és la simulació de dependències?
La simulació de dependències (mocking) és el procés de crear versions falses o simulades de les dependències d'un component o servei per a les proves. Aquestes versions simulades es comporten de manera controlada i previsible, permetent-nos provar el codi en aïllament.
Avantatges de la simulació de dependències
- Aïllament: Permet provar el codi sense dependre de les implementacions reals de les seves dependències.
- Control: Podem controlar el comportament de les dependències simulades per provar diferents escenaris.
- Fiabilitat: Les proves són més fiables perquè no depenen de factors externs com la xarxa o serveis externs.
Utilitzar Jasmine per simular dependències
Jasmine és el framework de proves utilitzat per defecte en Angular. Proporciona diverses eines per simular dependències, com ara spyOn i createSpyObj.
spyOn
spyOn és una funció de Jasmine que permet espiar (i opcionalment simular) els mètodes d'un objecte. Això és útil per verificar que un mètode s'ha cridat o per substituir el seu comportament durant una prova.
Exemple: Simulació d'un servei
Suposem que tenim un servei DataService amb un mètode getData que fa una sol·licitud HTTP per obtenir dades. Volem provar un component que utilitza aquest servei.
// data.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private http: HttpClient) {}
getData(): Observable<any> {
return this.http.get('https://api.example.com/data');
}
}// my-component.component.ts
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-my-component',
template: '<div>{{ data }}</div>'
})
export class MyComponent implements OnInit {
data: any;
constructor(private dataService: DataService) {}
ngOnInit(): void {
this.dataService.getData().subscribe(data => {
this.data = data;
});
}
}Per provar MyComponent, podem simular DataService utilitzant spyOn.
// my-component.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyComponent } from './my-component.component';
import { DataService } from './data.service';
import { of } from 'rxjs';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let dataService: DataService;
beforeEach(() => {
const dataServiceMock = {
getData: jasmine.createSpy('getData').and.returnValue(of({ key: 'value' }))
};
TestBed.configureTestingModule({
declarations: [MyComponent],
providers: [{ provide: DataService, useValue: dataServiceMock }]
}).compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
dataService = TestBed.inject(DataService);
});
it('should fetch data on init', () => {
fixture.detectChanges();
expect(dataService.getData).toHaveBeenCalled();
expect(component.data).toEqual({ key: 'value' });
});
});createSpyObj
createSpyObj és una altra funció de Jasmine que permet crear un objecte amb múltiples espies. Això és útil quan volem simular un servei amb diversos mètodes.
Exemple: Simulació d'un servei amb múltiples mètodes
Suposem que DataService té diversos mètodes que volem simular.
// data.service.ts
@Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private http: HttpClient) {}
getData(): Observable<any> {
return this.http.get('https://api.example.com/data');
}
postData(data: any): Observable<any> {
return this.http.post('https://api.example.com/data', data);
}
}Podem utilitzar createSpyObj per simular aquest servei.
// my-component.component.spec.ts
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let dataService: jasmine.SpyObj<DataService>;
beforeEach(() => {
const dataServiceMock = jasmine.createSpyObj('DataService', ['getData', 'postData']);
dataServiceMock.getData.and.returnValue(of({ key: 'value' }));
dataServiceMock.postData.and.returnValue(of({ success: true }));
TestBed.configureTestingModule({
declarations: [MyComponent],
providers: [{ provide: DataService, useValue: dataServiceMock }]
}).compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
dataService = TestBed.inject(DataService) as jasmine.SpyObj<DataService>;
});
it('should fetch data on init', () => {
fixture.detectChanges();
expect(dataService.getData).toHaveBeenCalled();
expect(component.data).toEqual({ key: 'value' });
});
it('should post data', () => {
component.postData({ key: 'value' });
expect(dataService.postData).toHaveBeenCalledWith({ key: 'value' });
});
});Exercicis pràctics
Exercici 1: Simular un servei senzill
- Crea un servei
UserServiceamb un mètodegetUserque retorna un observable amb un objecte d'usuari. - Crea un component
UserComponentque utilitzaUserServiceper obtenir l'usuari i mostrar el seu nom. - Escriu una prova unitària per
UserComponentsimulantUserServiceambspyOn.
Exercici 2: Simular un servei amb múltiples mètodes
- Afegeix un mètode
updateUseraUserServiceque actualitza l'usuari. - Modifica
UserComponentper utilitzarupdateUser. - Escriu una prova unitària per
UserComponentsimulantUserServiceambcreateSpyObj.
Solucions
Solució a l'exercici 1
// user.service.ts
@Injectable({
providedIn: 'root'
})
export class UserService {
getUser(): Observable<any> {
return of({ name: 'John Doe' });
}
}
// user.component.ts
@Component({
selector: 'app-user',
template: '<div>{{ user?.name }}</div>'
})
export class UserComponent implements OnInit {
user: any;
constructor(private userService: UserService) {}
ngOnInit(): void {
this.userService.getUser().subscribe(user => {
this.user = user;
});
}
}
// user.component.spec.ts
describe('UserComponent', () => {
let component: UserComponent;
let fixture: ComponentFixture<UserComponent>;
let userService: UserService;
beforeEach(() => {
const userServiceMock = {
getUser: jasmine.createSpy('getUser').and.returnValue(of({ name: 'John Doe' }))
};
TestBed.configureTestingModule({
declarations: [UserComponent],
providers: [{ provide: UserService, useValue: userServiceMock }]
}).compileComponents();
fixture = TestBed.createComponent(UserComponent);
component = fixture.componentInstance;
userService = TestBed.inject(UserService);
});
it('should fetch user on init', () => {
fixture.detectChanges();
expect(userService.getUser).toHaveBeenCalled();
expect(component.user).toEqual({ name: 'John Doe' });
});
});Solució a l'exercici 2
// user.service.ts
@Injectable({
providedIn: 'root'
})
export class UserService {
getUser(): Observable<any> {
return of({ name: 'John Doe' });
}
updateUser(user: any): Observable<any> {
return of({ success: true });
}
}
// user.component.ts
@Component({
selector: 'app-user',
template: '<div>{{ user?.name }}</div>'
})
export class UserComponent implements OnInit {
user: any;
constructor(private userService: UserService) {}
ngOnInit(): void {
this.userService.getUser().subscribe(user => {
this.user = user;
});
}
updateUser(user: any): void {
this.userService.updateUser(user).subscribe(response => {
if (response.success) {
this.user = user;
}
});
}
}
// user.component.spec.ts
describe('UserComponent', () => {
let component: UserComponent;
let fixture: ComponentFixture<UserComponent>;
let userService: jasmine.SpyObj<UserService>;
beforeEach(() => {
const userServiceMock = jasmine.createSpyObj('UserService', ['getUser', 'updateUser']);
userServiceMock.getUser.and.returnValue(of({ name: 'John Doe' }));
userServiceMock.updateUser.and.returnValue(of({ success: true }));
TestBed.configureTestingModule({
declarations: [UserComponent],
providers: [{ provide: UserService, useValue: userServiceMock }]
}).compileComponents();
fixture = TestBed.createComponent(UserComponent);
component = fixture.componentInstance;
userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
});
it('should fetch user on init', () => {
fixture.detectChanges();
expect(userService.getUser).toHaveBeenCalled();
expect(component.user).toEqual({ name: 'John Doe' });
});
it('should update user', () => {
const newUser = { name: 'Jane Doe' };
component.updateUser(newUser);
expect(userService.updateUser).toHaveBeenCalledWith(newUser);
expect(component.user).toEqual(newUser);
});
});Conclusió
La simulació de dependències és una tècnica poderosa per aïllar el codi que estem provant i assegurar-nos que les proves són fiables i repetibles. Utilitzant spyOn i createSpyObj de Jasmine, podem simular fàcilment serveis i altres dependències en les nostres proves unitàries. Amb la pràctica, aquesta tècnica esdevindrà una part essencial del vostre arsenal de proves.
Curs d'Angular
Mòdul 1: Introducció a Angular
- Què és Angular?
- Configuració de l'entorn de desenvolupament
- Arquitectura d'Angular
- Primera aplicació Angular
Mòdul 2: Components d'Angular
- Comprendre els components
- Crear components
- Plantilles de components
- Estils de components
- Interacció de components
Mòdul 3: Enllaç de dades i directives
- Interpolació i enllaç de propietats
- Enllaç d'esdeveniments
- Enllaç de dades bidireccional
- Directives integrades
- Directives personalitzades
Mòdul 4: Serveis i injecció de dependències
Mòdul 5: Enrutament i navegació
Mòdul 6: Formularis a Angular
Mòdul 7: Client HTTP i observables
- Introducció al client HTTP
- Fer sol·licituds HTTP
- Gestionar respostes HTTP
- Utilitzar observables
- Gestió d'errors
Mòdul 8: Gestió d'estat
- Introducció a la gestió d'estat
- Utilitzar serveis per a la gestió d'estat
- NgRx Store
- NgRx Effects
- NgRx Entity
Mòdul 9: Proves a Angular
- Proves unitàries
- Proves de components
- Proves de serveis
- Proves de cap a cap
- Simulació de dependències
Mòdul 10: Conceptes avançats d'Angular
- Angular Universal
- Optimització del rendiment
- Internacionalització (i18n)
- Tubs personalitzats
- Animacions d'Angular
