Angular + Testing Library + Jest
Tags: angular, testing, frontend, typescript
Published: 2021-09-07 07:51:08

##¿Por qué usar Testing Library?
Aquí os voy a dar mi opinión de porque usar **Testing Library** sobre angular, y como digo, es una opinión personal que he experimentado trasteando con Testing Library, aunque tengo que decir que llevo un par de días solamente.

_¡OJO! Esto no sustituye al framework que tu uses, sino que le da más funcionalidad o mejor dicho lo suplementa. Es cierto que recomiendan Jest, pero funciona con cualquier framework._

Lo poquito que llevo he visto, aporta un valor increíble a los test de Angular porque se centra en la interacción del usuario con nuestra aplicación. Yo, es cierto que flaqueaba en esta parte del testing, con esto creo que se facilita bastante la historia.

Vamos a comenzar sobre un proyecto de angular vacío…

##Quitar Karma y Jasmine
Vamos a usar **Jest**, por dos razones. Una, porque es lo que recomiendan desde Testing Library y dos, porque en el backend suelo usar Jest.

Desinstalamos Karma y Jasmine:
```bash
npm uninstall karma karma-chrome-launcher karma-coverage-istanbul-reporter karma-jasmine karma-jasmine-html-reporter

npm uninstall jasmine-core jasmine-spec-reporter @types/jasmine @types/jasmine
```
Borramos todas las configuraciones y referencias a Jasmine, Karma y a los ficheros de configuración de éstos.

Ficheros para borrar:
* karma.conf.js
* src/test.js

Referencias a src/test.js tenemos en tsconfig.spec.json y en angular.json, de momento borramos el de tsconfig.spec.json, aprovechamos y borramos tambien _jasmine_ del array de _types_:

![Archivo tsconfig.spec.json](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4vg5etajzbzvzuhdq7gm.png)

##Añadir Jest y configurarlo en Angular
En este punto tenemos nuestro proyecto Angular listo para instalarle otro test suite, test runner… en nuestro caso vamos a usar Jest.

Instalamos todo lo necesario para usar Jest en Angular:
```bash
npm i -D @types/jest jest jest-preset-angular ts-jest @angular-builders/jest
```
##Configuramos Jest
Al igual que teníamos un fichero de configuración karma.conf.json creamos nuestro fichero de configuración de Jest (jest.config.js).
 
![Fichero de configuración jest.config.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/64p5yzf5l62fmeimuiuk.png)

Aquí cargamos el preset que hemos instalado anteriormente y le decimos el fichero que queremos ejecutar antes de cada ejecución de cualquier test de nuestra suite. En ese fichero solamente tendremos el import del preset para Jest en Angular.

En el fichero tsconfig.spec.json donde antes teníamos *jasmine* tendremos que añadir nuestro nuevo framework de test *Jest* en la lista de "types".

Y en tsconfig tendremos que añadir estos flags (esModuleInterop y emitDecortatorMetadata):
 
![Fichero típico de tsconfig](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9i1mfn6yzdmtjz6vweuf.png)
 
¿Recordáis en pasos anteriores a borrar Karma y Jasmine comenté que, de momento, el fichero angular.json no lo íbamos a tocar?. Pues ahora hay que tocarlo.

Lo que vamos a hacer es cambiar la configuración de test dentro de ese fichero, así que borramos lo que hay dentro de "test" y añadimos la siguiente configuración:

![Parte de los tests del fichero angular.json](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3cnnhwz9tj8k7akf1lav.png)
 
Lo único que dejamos es el nuevo builder que tenemos instalado.

Falta añadir en nuestro package.json nuestros script para lanzar nuestros test:
```
"test": "jest --config ./jest.config.js"
```
##¿Y ahora?

Pues vamos a probar a hacer un test simple y vemos si todo está funcionando OK.

YO, he limpiado tanto el componente que crea Angular como su test, bueno su contenido.

Así que se me ha quedado de la siguiente manera el app.component.html
```html
<div>Hola Testing Library en Medium</div>
```
app.component.spec.ts
```javascript
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
    it('test my first test', async()=> {
        expect(1).toEqual(2);
    });
});
```
Este test fallará y aquí abajo os pongo la salida con el test fallando y una vez arreglado cambiando el 2 por un 1.
![Resultado del test fallando](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/03nmn957gonhgu8u9net.png)
![Resultado del test arreglado y pasando correctamente](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g29gzuhb7l68lykdzz0d.png)
 
##¿Donde está Testing Library?, porque yo no lo veo.

Hasta ahora no hemos visto nada de Testing Library, cierto. Así que vamos a instalarlo:

```bash
npm install --save-dev @testing-library/angular @testing-library/user-event
```

Aparte del básico de Angular he instalado user-event ya que lo vamos a usar si o si, como mínimo en este ejemplo.

He creado un segundo componente y este es el test que ha generado Angular-cli:
```typescript
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SecondTestComponent } from './second-test.component';
describe('SecondTestComponent', () => {
    let component: SecondTestComponent;
    let fixture: ComponentFixture<SecondTestComponent>;
    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [ SecondTestComponent ]
        }).compileComponents();
    }));
    
    beforeEach(() => {
       fixture = TestBed.createComponent(SecondTestComponent);
       component = fixture.componentInstance;
       fixture.detectChanges();
    });
    it('should create', () => {
        expect(component).toBeTruthy();
    });
});
```

Esta es la forma "nativa" de Angular que hay para instanciar/crear componentes en nuestro test. Vale, con Testing Library se simplifica la historia y este mismo test se podría quedar en…
```typescript
import { render, screen } from '@testing-library/angular';
import { SecondTestComponent } from './second-test.component';
describe('SecondTestComponent', () => {
    it('should create', async() => {
        await render(SecondTestComponent);
        expect(screen.getByText('second-test woraks!'));
    });
});
```
Con esto, que es lo básico vemos que nuestro componente se instancia correctamente.

Por último vamos a crear un input y ver la modificación de una variable al hacer click en un botón.
```html
<p>second-test works!</p>
<form [formGroup]="grettingForm">
    <input type="text" id="name-gretting" name="name" formControlName="name" />
</form>
<button (click)="grettingMe()">Saluda</button>
<div id="gretting">{{gretting}}</div>
```
El método que ejecutará el botón lo único que hará será "saludar" con el valor del input.
```typescript
grettingMe(): void {
   this.gretting = `Hola, ${this.grettingForm.controls.name.value}`;
}
```
Y este es nuestro test
```typescript
it('should gretting with input value', async () => {
    await render(SecondTestComponent, {
        imports: [ReactiveFormsModule]
    });
    const nameControl = document.querySelector('#name-gretting');
    userEvent.type(nameControl, 'Alberto');
    fireEvent.click(screen.getByText('Saluda'));
    expect(document.querySelector('#gretting').textContent)
    .toEqual('Hola, Alberto');
});
```
* Primero renderizamos el componente con los imports necesarios, en este caso el ReactiveFormsModule porque hemos creado un FormGroup.
* Recuperamos el input mediante querySelector.
* Con **userEvent** podemos "simular" las interacciones del usuario en este caso la escritura en nuestro input, con el método type.
*Ejecutamos un evento de click sobre el botón.
*Y comprobamos que nuestro div contiene el texto del saludo.

Eso sería un test básico, ya de aquí es evolucionar a cosas más complejas como llamadas a servicios y obtener datos de apis… pero con mocks y teniendo componentes que tengan poca responsabilidad, no debería de haber problemas y nuestro código se quedaría limpio, mantenible y super testeado.

En mi canal de Twitch, hice un directo con un componente de un proyecto "real". ([Canal twitch](https://www.twitch.tv/albertot_dev)).

Os dejo el código en mi repositorio.
Github: [Codigo](https://github.com/albertot-dev/testing-library-angular)

Podéis encontrarme en mis redes sociales: 
[Twitter](https://twitter.com/albertot_dev)
[Instagram](https://www.instagram.com/albertot_dev)
[Github](https://github.com/albertot-dev)
[Twitch](https://www.twitch.tv/albertot_dev)