Cómo usar espías en las pruebas angulares

Introducción
Los espías de Jasmine se utilizan para rastrear o crear funciones o métodos. Los espías son una forma de verificar si se llamó a una función o de proporcionar un valor de retorno personalizado. Podemos usar espías para probar componentes que dependen del servicio y evitar llamar a los métodos del servicio para obtener un valor. Esto ayuda a mantener nuestras pruebas unitarias enfocadas en probar los aspectos internos del componente en sí en lugar de sus dependencias.
En este artículo, aprenderá cómo usar espías Jasmine en un proyecto Angular.
Prerrequisitos
Para completar este tutorial, necesitarás:
- Node.js instalado localmente, lo cual puedes hacer siguiendo Cómo instalar Node.js y crear un entorno de desarrollo local .
- Algunos conocimientos sobre la configuración de un proyecto Angular .
Este tutorial fue verificado con Node v16.2.0, npm
v7.15.1 y @angular/core
v12.0.4.
Paso 1: Configuración del proyecto
Utilicemos un ejemplo muy similar al que usamos en nuestra introducción a las pruebas unitarias en Angular.
Primero, use @angular/cli
para crear un nuevo proyecto:
- ng new angular-test-spies-example
Luego, navegue hasta el directorio del proyecto recién creado:
- cd angular-test-spies-example
Anteriormente, la aplicación utilizaba dos botones para incrementar y disminuir valores entre 0 y 15.
En este tutorial, la lógica se trasladará a un servicio. Esto permitirá que varios componentes accedan al mismo valor central.
- ng generate service increment-decrement
Luego ábrelo increment-decrement.service.ts
en tu editor de código y reemplaza el contenido con el siguiente código:
src/app/incremento-decremento.servicio.ts
import { Injectable } from '@angular/core';@Injectable({ providedIn: 'root'})export class IncrementDecrementService { value = 0; message!: string; increment() { if (this.value 15) { this.value += 1; this.message = ''; } else { this.message = 'Maximum reached!'; } } decrement() { if (this.value 0) { this.value -= 1; this.message = ''; } else { this.message = 'Minimum reached!'; } }}
Ábrelo app.component.ts
en tu editor de código y reemplaza el contenido con el siguiente código:
src/app/app.component.ts
import { Component } from '@angular/core';import { IncrementDecrementService } from './increment-decrement.service';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']})export class AppComponent { constructor(public incrementDecrement: IncrementDecrementService) { } increment() { this.incrementDecrement.increment(); } decrement() { this.incrementDecrement.decrement(); }}
Ábrelo app.component.html
en tu editor de código y reemplaza el contenido con el siguiente código:
src/aplicación/aplicación.componente.html
h1{{ incrementDecrement.value }}/h1hrbutton (click)="increment()"Increment/buttonbutton (click)="decrement()"Decrement/buttonp {{ incrementDecrement.message }}/p
A continuación, abra app.component.spec.ts
su editor de código y modifique las siguientes líneas de código:
src/app/app.component.spec.ts
import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing';import { By } from '@angular/platform-browser';import { DebugElement } from '@angular/core';import { AppComponent } from './app.component';import { IncrementDecrementService } from './increment-decrement.service';describe('AppComponent', () = { let fixture: ComponentFixtureAppComponent; let debugElement: DebugElement; let incrementDecrementService: IncrementDecrementService; beforeEach(waitForAsync(() = { TestBed.configureTestingModule({ declarations: [ AppComponent ], providers: [ IncrementDecrementService ] }).compileComponents(); fixture = TestBed.createComponent(AppComponent); debugElement = fixture.debugElement; incrementDecrementService = debugElement.injector.get(IncrementDecrementService); })); it('should increment in template', () = { debugElement .query(By.css('button.increment')) .triggerEventHandler('click', null); fixture.detectChanges(); const value = debugElement.query(By.css('h1')).nativeElement.innerText; expect(value).toEqual('1'); }); it('should stop at 15 and show maximum message', () = { incrementDecrementService.value = 15; debugElement .query(By.css('button.increment')) .triggerEventHandler('click', null); fixture.detectChanges(); const value = debugElement.query(By.css('h1')).nativeElement.innerText; const message = debugElement.query(By.css('p.message')).nativeElement.innerText; expect(value).toEqual('15'); expect(message).toContain('Maximum'); });});
Observe cómo podemos obtener una referencia al servicio inyectado con debugElement.injector.get
.
Probar nuestro componente de esta manera funciona, pero también se realizarán llamadas reales al servicio y nuestro componente no se prueba de forma aislada. A continuación, exploraremos cómo usar espías para verificar si se han llamado a métodos o para proporcionar un valor de retorno de código auxiliar.
Paso 2: Espiar los métodos de un servicio
A continuación se muestra cómo usaría spyOn
la función de Jasmine para llamar a un método de servicio y probar que se llamó:
src/app/app.component.spec.ts
import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing';import { By } from '@angular/platform-browser';import { DebugElement } from '@angular/core';import { AppComponent } from './app.component';import { IncrementDecrementService } from './increment-decrement.service';describe('AppComponent', () = { let fixture: ComponentFixtureAppComponent; let debugElement: DebugElement; let incrementDecrementService: IncrementDecrementService; let incrementSpy: any; beforeEach(waitForAsync(() = { TestBed.configureTestingModule({ declarations: [ AppComponent ], providers: [ IncrementDecrementService ] }).compileComponents(); fixture = TestBed.createComponent(AppComponent); debugElement = fixture.debugElement; incrementDecrementService = debugElement.injector.get(IncrementDecrementService); incrementSpy = spyOn(incrementDecrementService, 'increment').and.callThrough(); })); it('should call increment on the service', () = { debugElement .query(By.css('button.increment')) .triggerEventHandler('click', null); expect(incrementDecrementService.value).toBe(1); expect(incrementSpy).toHaveBeenCalled(); });});
spyOn
toma dos argumentos: la instancia de la clase (nuestra instancia de servicio en este caso) y un valor de cadena con el nombre del método o función a espiar.
Aquí también hemos encadenado .and.callThrough()
el espía, por lo que se seguirá llamando al método real. En este caso, nuestro espía solo se utiliza para saber si el método se ha llamado realmente y para espiar los argumentos.
A continuación se muestra un ejemplo de cómo afirmar que un método se llamó dos veces:
expect(incrementSpy).toHaveBeenCalledTimes(2);
A continuación se muestra un ejemplo de cómo afirmar que no se llamó a un método con el argumento 'error'
:
expect(incrementSpy).not.toHaveBeenCalledWith('error');
Si queremos evitar llamar a los métodos del servicio podemos usarlos .and.returnValue
en el espía.
Nuestros métodos de ejemplo no son buenos candidatos para esto porque no devuelven nada y en su lugar mutan propiedades internas.
Agreguemos un nuevo método a nuestro servicio que realmente devuelva un valor:
src/app/incremento-decremento.servicio.ts
minimumOrMaximumReached() { return !!(this.message this.message.length);}
Nota: el uso !!
antes de una expresión convierte el valor en un booleano.
También agregamos un nuevo método a nuestro componente que será utilizado por la plantilla para llegar al valor:
src/app/app.component.ts
limitReached() { return this.incrementDecrement.minimumOrMaximumReached();}
Ahora nuestra plantilla muestra un mensaje si se alcanza el límite con esto:
src/aplicación/aplicación.componente.html
p *ngIf="limitReached()" Limit reached!/p
Luego podemos probar que nuestro mensaje de plantilla mostrará si se alcanza el límite sin tener que recurrir a llamar al método en el servicio:
src/app/app.component.spec.ts
import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing';import { By } from '@angular/platform-browser';import { DebugElement } from '@angular/core';import { AppComponent } from './app.component';import { IncrementDecrementService } from './increment-decrement.service';describe('AppComponent', () = { let fixture: ComponentFixtureAppComponent; let debugElement: DebugElement; let incrementDecrementService: IncrementDecrementService; let minimumOrMaximumSpy: any; beforeEach(waitForAsync(() = { TestBed.configureTestingModule({ declarations: [ AppComponent ], providers: [ IncrementDecrementService ] }).compileComponents(); fixture = TestBed.createComponent(AppComponent); debugElement = fixture.debugElement; incrementDecrementService = debugElement.injector.get(IncrementDecrementService); minimumOrMaximumSpy = spyOn(incrementDecrementService, 'minimumOrMaximumReached').and.returnValue(true); })); it(`should show 'Limit reached' message`, () = { fixture.detectChanges(); const message = debugElement.query(By.css('p.message')).nativeElement.innerText; expect(message).toEqual('Limit reached!'); });});
Conclusión
En este artículo, aprendiste a usar espías Jasmine en un proyecto Angular.
Si desea obtener más información sobre Angular, consulte nuestra página de temas de Angular para ver ejercicios y proyectos de programación.
Deja una respuesta