Cómo usar espías en las pruebas angulares

Introducción

Índice
  1. Introducción
  • Prerrequisitos
  • Paso 1: Configuración del proyecto
  • Paso 2: Espiar los métodos de un servicio
  • Conclusió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, npmv7.15.1 y @angular/corev12.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/clipara crear un nuevo proyecto:

    1. ng new angular-test-spies-example

    Luego, navegue hasta el directorio del proyecto recién creado:

    1. 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.

    1. ng generate service increment-decrement

    Luego ábrelo increment-decrement.service.tsen 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.tsen 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.htmlen 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.tssu 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 spyOnla 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();  });});

    spyOntoma 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.returnValueen 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.

    SUSCRÍBETE A NUESTRO BOLETÍN 
    No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

    Subir

    Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion