Componentes con Outputs
Clase 8 de 23 • Curso de Angular: Unit Testing para Componentes
Contenido del curso
Clase 8 de 23 • Curso de Angular: Unit Testing para Componentes
Contenido del curso
Carlos Alejandro Hernández Mejía
Cesar Elías Armendariz Ruano
David Adriazola Muriel
Germán Arevalo Jerez
Jorge Luis Silva Medina
Juan Luna
Daniel Córdova
Brayam Esteban Quiñones Sanchez
Jose Nuñez
Jorge Luis Silva Medina
Outputs
Para testear atributos con el decorador @Output en Angular, puedes seguir estos pasos:
Configura el componente que emite el @Output para que ejecute una acción cuando cambie el valor del @Output.
onEvent(): void { this.myOutput.emit('some value'); }
Utiliza DebugElement para simular un evento en el elemento del DOM que cambia el valor del @Output:
const debugElement: DebugElement = fixture.debugElement.query(By.css('button[name="my-button"]'));
Verifica si la acción se ha ejecutado correctamente cuando ha cambiado el valor del @Output. Esto puede hacerse utilizando cualquiera de estas opciones:
Utilizar una variable que se cambie cuando se ejecuta la acción y verificar su estado con "expect":
// Arrange let flag = false; // Act fixture.componentInstance.myOutput.subscribe((value) => { flag = true; }); debugElement.triggerEventHandler('click', null}); // Assert expect(flag).toBeTruthy();
Utilizar una función espía de Jasmine para verificar si se ha llamado a la función cuando ha cambiado el valor del @Output:
// Arrange const spy = spyOn(fixture.componentInstance, 'onEvent').and.callThrough(); const spyEmit = spyOn(fixture.componentInstance.myOutput, 'emit').and.callThrough(); // Act debugElement.triggerEventHandler('click', null}); // Assert expect(spy).toHaveBeenCalled(); expect(spyEmit).toHaveBeenCalled();
Se puede realizar las pruebas sobre parámetros de salida como los outputs, de la siguiente manera
primero creamos el output de salida y el evento que lo dispara
person.component.ts
import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'; import { Person } from 'src/app/models/person.model'; export class PersonComponent implements OnInit { @Input() person!: Person; @Output() onSelected = new EventEmitter<Person>(); imc = ''; onClick() { this.onSelected.emit(this.person); } }
luego creamos el botón que dispara el evento
person.component.html
<button class="btn-choose" (click)="onClick()">Choose</button>
creamos la prueba
person.component.spec.ts
it('should raise selected event when do click', () => { // Arrange const expectedPerson = new Person('Juan', 'Perez', 30, 120, 1.65); component.person = expectedPerson; const buttonDe = fixture.debugElement.query(By.css('button.btn-choose')); let selectedPerson: Person | undefined; component.onSelected .subscribe(person => selectedPerson = person); // Act buttonDe.triggerEventHandler('click', null); fixture.detectChanges(); // Assert expect(selectedPerson).toEqual(expectedPerson); })
Cuando nos subscribimos al evento "onSelected" no es necesario usar el callback "doneFn"?? por que en otras pruebas que utilizan el subscribe se las usa ?? y en esta prueba no es necesaria??
buena pregunta🤔
Es correcto de preferencia al manejar los subscribe es recomendable manejar en doneFn yo asi lo escribi y me funciono correctamente
it('should raise selected event when click', (doneFn) => { // Arrange const expectPerson = new Person('Jorge', 'Medina', 25, 67.2, 1.81); component.person = expectPerson; const buttonDebug: DebugElement = fixture.debugElement.query( By.css('button.btn-choose') ); let selectedPerson: Person | undefined; component.onSelected.subscribe((person) => { selectedPerson = person; expect(selectedPerson).toEqual(expectPerson); doneFn(); }); // Act buttonDebug.triggerEventHandler('click', null); fixture.detectChanges(); // Assert });
Una observación, no sé qué pensaréis. Yo en mis desarrollos suelo usar clases de css para los tests, con unos prefijos específicos, sean unitarios o e2e con cypress, ...
\<button class="ng-testing-id-imc" (click)="calcIMC()">IMC: {{ imc }}\</button> \<button class="ng-testing-id-choose" (click)="onSelect()">
Estos prefijos ayudan a distinguir claramente qué elementos están destinados específicamente para pruebas y no para estilos de diseño.
Al emplear este enfoque, los desarrolladores y los equipos de prueba pueden identificar fácilmente los elementos que necesitan para escribir pruebas de integración o pruebas end-to-end. Además, al utilizar prefijos descriptivos como ng-testing-id- o cy-e2e-testing-id-, se facilita la comprensión del propósito de estas clases y se reduce la posibilidad de conflictos con clases de estilo utilizadas en la aplicación.
Como podriamos emular event.preventDefault() desde el archivo de pruebas con el triggerEventHandler en vez de pasar un null como segundo parametro?
Tengo el siguente funcion cuando hago click en un button:
onSaveForm(event: Event) { event.preventDefault(); /// resto de la funcion }
Hola Daniel, lo que podrias hacer es crear un objeto que tenga esta funcion, asi podrias simular este comportamiento.
const Event = { preventDefault: () => {} }
Incluso podiras hacer un spy y ver si realmente se esta llamando, en este ejemplo que te comporto, lo ejecuto dos veces para demostrarlo.
it('should raise select event when do click', () => { //Arrange const expectPerson = new Person('Esteban', 'Qs', 27, 120, 1.65); const Event = { preventDefault: () => {} } component.person = expectPerson; const buttonDebug = fixture.debugElement.query(By.css('button.btn-choose')); const spyEvent = spyOn(Event, 'preventDefault'); let selectedPerson: Person | undefined; component.onSelected. subscribe((person) => { selectedPerson = person; }); //Act buttonDebug.triggerEventHandler('click', Event); fixture.detectChanges(); //Assert expect(spyEvent).toHaveBeenCalledTimes(2); expect(selectedPerson).toEqual(expectPerson); });
person.component.ts
onClick(event: Event): void { event.preventDefault(); event.preventDefault(); this.onSelected.emit(this.person); }
person.component.html
<button class="btn-choose" (click)="onClick($event)">Chooser</button>
Es interesante lo de los @Output(), a mi me sale este warning al intentar nombrarlos como 'onSelected'.
Output bindings, including aliases, should not be named "on", nor prefixed with it (https://angular.io/guide/styleguide#style-05-16)eslint@angular-eslint/no-output-on-prefix
Genera una función en person.model.ts
calcBirthYear(): number { if (this.age < 0) { return 0; } const date = new Date(); const currentYear = date.getFullYear(); const birthYear = currentYear - this.age; return birthYear; }
donde genere su set de pruebas correspondiente
describe('test for calcBirthYear', () => { it('should return year of birth', () => { // Arrange // Act const rta = person.calcBirthYear(); // Assert expect(rta).toEqual(1998); }); it('should return 0', () => { // Arrange person.age = -10; // Act const rta = person.calcBirthYear(); // Assert expect(rta).toEqual(0); }); });
Agregandolo como un EventEmmiter
it('should raise calcBirthYear event when click', (doneFn) => { // Arrange component.person = new Person('Jorge', 'Medina', 25, 67.2, 1.81); const expectBirthYear = 1998; const buttonDebug: DebugElement = fixture.debugElement.query( By.css('button.btn-age') ); component.onCalcBirthYear.subscribe((birthYear) => { // Assert expect(birthYear).toEqual(expectBirthYear); doneFn(); }); // Act buttonDebug.triggerEventHandler('click', null); fixture.detectChanges(); });