Angular Testing Library - Fundamentos
Published: 2023-01-23 20:16:19

Para realizar pruebas en Angular es necesario saber lo siguiente:

**Jest** 
Como framework
Obtenemos las funciones necesarias para ejecutar nuestras pruebas.

**Testing Library**
Como utilidades para las pruebas
Envuelve al componente en nodos DOM para consultar e interactuar con él.

**Gherkin Reference** - Cucumber Documentation 
Como referencia para el nombramiento de las pruebas
Nos da una estructura de nombramiento basado en BDD (Desarrollo orientado al comportamiento)

## Testing Library
El principio detrás de Testing Library es: 

> "Cuanto más se parezcan sus pruebas a la forma en que se usa su software, más confianza le pueden brindar"

Testing Library también lo empuja a escribir más pruebas de integración y menos pruebas unitarias. Esto es importante porque una aplicación Angular puede estar compuesta por muchos componentes. Probar un componente individual es importante, pero podría decirse que probar _cómo funcionan todos estos componentes juntos es más importante_.

A sus usuarios no les importa cómo está estructurada su aplicación o qué tecnología está utilizando. Les importa que puedan interactuar con su aplicación y que funcione sin problemas.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xss268qfnp5h88nskpws.png)

## Creación de un conjunto de pruebas
Una prueba consta de una o más suites. Una suite se declara con un bloque `describe`:
```javascript
describe('Suite description', () => {
  /* … */
});
```

Los bloques `describe` se pueden anidar para estructurar grandes conjuntos y dividirlos en secciones lógicas:
```javascript
describe('Suite description', () => {
  describe('One aspect', () => {
    /* … */
  });
  describe('Another aspect', () => {
    /* … */
  });
});
```

### Especificaciones
Cada suit consta de una o más especificaciones llamadas `it` (independent test)
```javascript
describe('Suite description', () => {
  it('Spec description', () => {
    /* … */
  });
  /* … more specs …  */
});
```

> El nombre que se le debe dar al `it` debe afirmar el comportamiento del código bajo prueba en lenguaje humano. 

> Se recomienda que el nombre de la prueba esté bajo esta estructura: `When the orders are loaded, then it should display the data correctly`

```javascript
it('When the orders are loaded, then it should display the data correctly', () => {
  /* … */
});
```

Donde:

1.`When`: Se trata de las condiciones de las acciones que se van a ejecutar
2.`Then`: Se trata del resultado esperado de las acciones ejecutadas

Este formato se inspira en el lenguaje Gherkin. [Ver más sobre lenguaje Gherkin](https://profile.es/blog/que-es-gherkin/)

### Estructura de un test
Dentro del bloque `it` se encuentra el código de prueba real. El código de prueba generalmente consta de tres fases: **Organizar (Arrange)**, **Actuar (Act)** y **Afirmar (Asserts)**.

Ejemplo:

1. Organizar:
a. Crear una instancia de ExampleComponent.
b. Rederizar el Componente en el documento.

2. Actuar:
a. Busque y enfoque el campo de entrada .
b. Introduzca el texto "5".
c. Busque y haga clic en el botón "Aceptar".

3. Afirmar:
a. Espere que el numero mostrado ahora lea "5".

```javascript
import '@testing-library/jest-dom'
import userEvent from '@testing-library/user-event'
import { render } from '@testing-library/angular'
import { MatInputModule } from '@angular/material/input'
import { MatButtonModule } from '@angular/material/button'
import { MatFormFieldModule } from '@angular/material/form-field'

it('Example title', async () => {
  // Arrange
  const user = userEvent.setup()
  const { fixture } = await render(ExampleComponent, { 
    declarations: [ReactiveFormsModule, MatFormFieldModule, MatButtonModule, MatInputModule],
    imports: [],
    providers: []
  })
  
  //Act
  const priceControl = screen.getByLabelText(/Price/i)
  await user.type(priceControl, '5')
  await user.click(screen.getByText(/Save/i))
  
  //Assert
  const content = screen.getByTestId('example_message_label'))
  expect(content).toHaveTextContent('Hi 5!')
})
```

### Conjunto de pruebas
Al escribir una `suit` podrá haber casos donde hay código que se repite por cada `it`. Para ello existen otras funciones que se ejecutan antes o despues de cada `it`

```javascript
describe('Suite description', () => {
  beforeAll(() => {
    console.log('Called before all specs are run');
  });
  afterAll(() => {
    console.log('Called after all specs are run');
  });

  beforeEach(() => {
    console.log('Called before each spec is run');
  });
  afterEach(() => {
    console.log('Called after each spec is run');
  });

  it('Spec 1', () => {
    console.log('Spec 1');
  });
  it('Spec 2', () => {
    console.log('Spec 2');
  });
});
```
Para más información, [ver la documenación de Jest](https://jestjs.io/docs/setup-teardown)

### Mocking
La idea detrás del `mock` es la siguiente: Cuando queremos probar una función, muchas veces su resultado dependen de la respuesta de otras funciones, entonces podemos hacer mock a esas otras funciones para simular su respuesta.

En el siguiente ejemplo tenemos la función `thumbWar()` que simula un juego al azar donde una de las 2 personas gana después de 2 intentos y la función `getWinner()` que aleatoriamente trae 1 ganador.

{% embed https://codesandbox.io/embed/angular-testing-library-qpvb0n?fontsize=13&module=%2Ftest-demo%2Findex.spec.ts&theme=dark&view=editor %}

En este caso se usa `jest.spyOn()` para hacer “mock" a la función getWinner() y que siempre traiga la primera persona.

Lo bueno de usar jest.spyOn() es que podemos recuperar la implementación anterior, usando `[nom-var].mockRestore()` que servirá para no entrar en conflicto con otros tests en un mismo archivo.

> También se puede usar `jest.fn()`, pero no se podrá recuperar la implementación original

Adicionalmente con la implementación de `jest.spyOn()` o `jest.fn()` podemos hacer rastreo de llamadas del método. Ejm:

```javascript
const spy = jest.spyOn(...)

// Saber si el metodo ha sido llamado con determinados parametros de entrada
expect(spy).toHaveBeenCalledWith(arg1, arg2)

// Saber si ha sido llamado
expect(spy).toHaveBeenCalled()

// Saber si ha sido llamado determinadas veces
expect(spy).toHaveBeenCalledTimes(number)

// Saber si ha retornado alguna variable
expect(spy).toHaveReturnedWith(value)

// Etc...
```

Para mas información ver el siguiente [LINK](https://jestjs.io/docs/expect#tohavebeencalled)

### Métodos más usados para las aserciones

```javascript
// Saber si valor es igual a otro valor
expect(form.valid).toEqual(false)

// Saber si un elemento tiene un atributo
expect(toggleEnabled).toHaveAttribute('ng-reflect-checked', true)

// Saber si un objecto tiene una determinada propiedad
expect(form.clientCode?.errors).toHaveProperty('clientCodeExist', true)

// Saber si un elemento tiene un determinado texto como contenido
expect(screen.getByTestId('orderTable_status_label')).toHaveTextContent('CANCELATION REQUESTED')

// Saber si un texto o elemento está en el documento
expect(screen.getByText('Distributor reason:')).toBeInTheDocument()
```

Si queremos negar las aserciones le antepodemos `.not`

```javascript
// Saber si un texto o elemento NO está en el documento
expect(screen.getByText('Distributor reason:')).not.toBeInTheDocument()
```