Cómo empezar a utilizar pruebas unitarias en Angular

Introducción
Si su proyecto se creó utilizando Angular CLI , todo estará listo para que pueda comenzar a escribir pruebas utilizando Jasmine como marco de prueba y Karma como ejecutor de pruebas.
Angular también proporciona utilidades como TestBed
y async
para facilitar la prueba de código asincrónico, componentes, directivas o servicios.
En este artículo, aprenderá a escribir y ejecutar pruebas unitarias en Angular usando Jasmine y Karma.
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
Los archivos de prueba generalmente se colocan junto a los archivos que prueban, pero también pueden estar en su propio directorio separado si lo prefiere.
Estos archivos de especificaciones utilizan la convención de nombres *.spec.ts
.
Primero, use @angular/cli
para crear un nuevo proyecto:
- ng new angular-unit-test-example
Luego, navegue hasta el directorio del proyecto recién creado:
- cd angular-unit-test-example
Junto a app.component
, habrá un app.component.spec.ts
archivo. Abra este archivo y examine su contenido:
src/app/app.component.spec.ts
import { TestBed } from '@angular/core/testing';import { AppComponent } from './app.component';describe('AppComponent', () = { beforeEach(async () = { await TestBed.configureTestingModule({ declarations: [ AppComponent ], }).compileComponents(); }); it('should create the app', () = { const fixture = TestBed.createComponent(AppComponent); const app = fixture.componentInstance; expect(app).toBeTruthy(); }); it(`should have as title 'angular-unit-test-example'`, () = { const fixture = TestBed.createComponent(AppComponent); const app = fixture.componentInstance; expect(app.title).toEqual('angular-unit-test-example'); }); it('should render title', () = { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.nativeElement; expect(compiled.querySelector('.content span').textContent).toContain('angular-unit-test-example app is running!'); });});
Entendiendo a Jasmine
Primero, algunas cosas que es importante saber sobre el jazmín:
describe
Los bloques definen un conjunto de pruebas y cadait
bloque es para una prueba individual.beforeEach
Se ejecuta antes de cada prueba y se utiliza para lasetup
parte de una prueba.afterEach
Se ejecuta después de cada prueba y se utiliza para lateardown
parte de una prueba.- También puedes utilizar
beforeAll
yafterAll
, y estos se ejecutan una vez antes o después de todas las pruebas. - Para probar una afirmación en Jasmine,
expect
utilice y un comparador comotoBeDefined
,toBeTruthy
,toContain
,toEqual
,toThrow
,toBeNull
, … Por ejemplo:expect(myValue).toBeGreaterThan(3);
- Puedes hacer una afirmación negativa con
not
:expect(myValue).not.toBeGreaterThan(3);
- También puedes definir comparadores personalizados.
TestBed
es la utilidad principal disponible para las pruebas específicas de Angular. La usarás TestBed.configureTestingModule
en beforeEach
el bloque de tu conjunto de pruebas y le darás un objeto con valores similares a los de un componente regular NgModule
para declarations
, providers
y imports
. Luego puedes encadenar una llamada a compileComponents
para indicarle a Angular que compile los componentes declarados.
Puede crear un component fixture
con TestBed.createComponent
. Los accesorios tienen acceso a un debugElement
, lo que le dará acceso a los componentes internos del accesorio.
La detección de cambios no se realiza automáticamente, por lo que deberá llamar detectChanges
a un accesorio para indicarle a Angular que ejecute la detección de cambios.
Envolver la función de devolución de llamada de una prueba o el primer argumento de beforeEach
with async
permite a Angular realizar una compilación asincrónica y esperar hasta que el contenido dentro del async
bloque esté listo antes de continuar.
Entendiendo las pruebas
Esta primera prueba se denomina should create the app
y se utiliza expect
para verificar la presencia del componente con toBeTruthy()
.
La segunda prueba se nombra should have as title 'angular-unit-test-example'
y se utiliza expect
para verificar que el app.title
valor sea igual a la cadena 'angular-unit-test-example'
con toEqual()
.
La tercera prueba se nombra should render title
y se utiliza expect
para comprobar el código compilado para el texto 'angular-unit-test-example app is running!'
con toContain()
.
En su terminal, ejecute el siguiente comando:
- ng test
Se ejecutarán las tres pruebas y aparecerán los resultados de las mismas:
Output3 specs, 0 failures, randomized with seed 84683AppComponent* should have as title 'angular-unit-test-example'* should create the app* should render title
Las tres pruebas se están superando actualmente.
Paso 2: creación de un componente de ejemplo
Creemos un componente que incremente o disminuya un valor.
Abra app.component.ts
su editor de código y reemplace las siguientes líneas de código con la lógica increment
y decrement
:
src/app/app.component.ts
import { Component } from '@angular/core';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']})export class AppComponent { 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.html
en tu editor de código y reemplaza el contenido con el siguiente código:
src/aplicación/aplicación.componente.html
h1{{ value }}/h1hrbutton (click)="increment()"Increment/buttonbutton (click)="decrement()"Decrement/buttonp {{ message }}/p
En este punto, deberías tener versiones revisadas de app.component.ts
y app.component.html
.
Paso 3: creación del conjunto de pruebas
Vuelva a visitar app.component.spec.ts
su editor de código y reemplácelo con estas líneas de código:
src/app/app.component.spec.ts
import { TestBed, async, ComponentFixture } from '@angular/core/testing';import { By } from '@angular/platform-browser';import { DebugElement } from '@angular/core';import { AppComponent } from './app.component';describe('AppComponent', () = { let fixture: ComponentFixtureAppComponent; let debugElement: DebugElement; beforeEach(async(() = { TestBed.configureTestingModule({ declarations: [ AppComponent ], }).compileComponents(); fixture = TestBed.createComponent(AppComponent); debugElement = fixture.debugElement; })); it('should increment and decrement value', () = { fixture.componentInstance.increment(); expect(fixture.componentInstance.value).toEqual(1); fixture.componentInstance.decrement(); expect(fixture.componentInstance.value).toEqual(0); }); it('should increment value 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 0 and show minimum message', () = { debugElement .query(By.css('button.decrement')) .triggerEventHandler('click', null); fixture.detectChanges(); const message = debugElement.query(By.css('p.message')).nativeElement.innerText; expect(fixture.componentInstance.value).toEqual(0); expect(message).toContain('Minimum'); }); it('should stop at 15 and show maximum message', () = { fixture.componentInstance.value = 15; debugElement .query(By.css('button.increment')) .triggerEventHandler('click', null); fixture.detectChanges(); const message = debugElement.query(By.css('p.message')).nativeElement.innerText; expect(fixture.componentInstance.value).toEqual(15); expect(message).toContain('Maximum'); });});
Asignamos fixture
y debugElement
directamente en el beforeEach
bloque porque todas nuestras pruebas los necesitan. También los tipificamos fuertemente al importar ComponentFixture
desde @angular/core/testing
y DebugElement
desde @angular/core
.
En nuestra primera prueba, llamamos a métodos en la propia instancia del componente.
En las pruebas restantes, usamos nuestro DebugElement
para activar los clics de los botones. Observe cómo DebugElement
tiene un query
método que toma un predicado. Aquí usamos la By
utilidad y su css
método para encontrar un elemento específico en la plantilla. DebugElement
también tiene un nativeElement
método para acceder directamente al DOM.
También usamos fixture.detectChanges
en las últimas 3 pruebas para indicarle a Angular que ejecute la detección de cambios antes de realizar nuestras afirmaciones con Jasmine expect
.
Una vez que hayas realizado los cambios, ejecuta el ng test
comando desde la terminal:
- ng test
Esto iniciará Karma en modo de observación, por lo que sus pruebas se volverán a compilar cada vez que cambie un archivo.
Output4 specs, 0 failures, randomized with seed 27239AppComponent* should increment value in template* should increment and decrement value* should stop at 0 and show minimum message* should stop at 15 and show maximum message
Las cuatro pruebas serán aprobadas.
Conclusión
En este artículo, aprenderá a escribir y ejecutar pruebas unitarias en Angular con Jasmine y Karma. Ahora que conoce las principales utilidades de prueba de Angular, puede comenzar a escribir pruebas para componentes simples.
Continúe su aprendizaje probando componentes con dependencias, probando servicios y utilizando mocks, stubs y spys.
También puedes consultar la documentación oficial para obtener una guía detallada sobre pruebas de Angular.
Deja una respuesta