---
# System prepended metadata

title: Testing de componentes en React

---

# Testing de componentes en React

## Instalación de dependencias
Para testear componentes en React necesitaremos un entorno en base a Jest y React Testing Library. Además de tener evidentemente un proyecto en React funcionando, con todas sus dependencias.

```bash!
$npm install --save-dev jest @testing-library/react @testing-library/jest-dom jest-environment-jsdom
```

Además debemos configurar Jest para que sea capaz de utilizar jsdom para hacer un render fake de nuestros componentes.

*jest.config.js*
```javascript!
const nextJest = require("next/jest")

const createJestConfig = nextJest({
    dir: "./src",
})

const customJestConfig = {
    setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
    moduleDirectories: ["node_modules", "<rootDir>/"],
    testEnvironment: "jest-environment-jsdom",
    // En caso de utilizar alias
    moduleNameMapper: {
        "@components/(.*)": "<rootDir>/src/components/$1",
        "@containers/(.*)": "<rootDir>/src/containers/$1",
        "@pages/(.*)": "<rootDir>/src/pages/$1",
    },
}

module.exports = createJestConfig(customJestConfig)
```

*jest.setup.js*
```javascript!
// Optional: configure or set up a testing framework before each test.
// If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js`

// Used for __tests__/testing-library.js
// Learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom/extend-expect"

```

## Testing Library
Esta librería está hecha de tal forma que se simula el comportamiento del usuario final con los componentes. Como usuario no vamos a saber como es el árbol DOM que hay por debajo, interectuaremos a través de elementos visuales que nos sirvan de referencia.
A la hora de desarrollar los tests es importante que adoptemos ese enfoque, y hacer tests basados en casos de uso y no en el código.

### Orden de Prioridad
Testing library en su documentación nos anota una serie de recomendaciones sobre prioridades a la hora de seleccionar elementos.

[Prioridades Testing Library](https://testing-library.com/docs/queries/about/#priority)

### Funciones interesantes
- **toHaveTextContent**: Busca el texto que le pasamos por parámetro.

```javascript!
/*...*/
expect(container).toHaveTextContent(title)
/*...*/
```

- **toBeInDocument**: Busca el elemento en el DOM simulado.

```javascript!
/*...*/
expect(heading).toBeInTheDocument()
/*...*/
```

- **toMatchSnapShot**: Busca el contenedor o elemento que pasemos en el SnapShot que crea Jest.
```javascript!
/*...*/
expect(container).toMatchSnapshot()
/*...*/
```

- **component.getByText**: Busca un elemento por el texto que pasemos por parámetros.

```javascript!
/*...*/
const registerBtn = component.getByText("Crear cuenta")
/*...*/
```

- **component.debug**: Te permite visualizar lo que se está renderizando cuándo ejecutas los tests.

```jsx!
const component = render(<Component />)

component.debug()
```

- **prettyDOM**: Te permite ver de una forma más bonita el componente o elemento que le pasemos por parámetro.

```javascript!
const component = render(<Component />)
const button = component.getByText("hello")

console.log(prettyDOM(button))
```

- **element.click**: Ejecuta el evento onClick en el elemento.

```javascript!
/*...*/
const registerBtn = component.getByText("Crear cuenta")

registerBtn.click()
/*...*/
```

- Cuando busques elementos por text, label, etc. Es recomendable usar el siguiente tipo de expresión regular, pues en caso de que pasemos alguna mayúscula / minúscula nos seguirá funcionando.

```javascript=
/*...*/
const registerBtn = component.getByText(/Crear cuenta/i)
/*...*/
```

- Podemos hacer uso de la función within, esta se utiliza para seleccionar un elemento del DOM dentro de un contenedor específico.

```javascript=
/*...*/
const elementoPadre = document.querySelector(".padre")
// Buscamos aquellas coincidencias que existan dentro del contenedor padre
const elementoHijo = within(elementoPadre).getByText('hijo')
/*...*/
```

- En caso de querer especifcar que algo "no sea", debemos utilizar el modificador .not

```javascript=
expect(screen.queryByText(/Hola/i).not.toBeInTheDocument())
```

- Con *render* podemos renderizar un componente dentro del test y probarlo dentro de este.

```javascript=
const component = render(<Component />)
```

- **findBy()**: test asíncronos. Se utiliza cuando esperamos que aparezca un elemento en el DOM, pero que igual esto no ocurre al momento.

```javascript=
fireEvent.click(button)
await screen.findByText('He sido clicado')
```

- **waitFor()**: test asíncrono, Espera a que se cumpla la condición que le hemos establecido antes de continuar.

```javascript=
await waitForElementToBeRemoved(() =>
    screen.queryByText(/Me voy a borrar/i)
);
```

- **toHaveBeenCalled()**: se utiliza para comprobar que se ha llamado a una función en concreto.

```javascript=
/*...*/
funcionEjemplo()
expect(funcionEjemplo).toHaveBeenCalled()
/*...*/
```

- **toHaveReturned()**: para comprobar que una función se ha ejecutado correctamente, es decir, no devuelve un error.

```javascript=
/*...*/
const funcionEjemplo = () => true
funcionEjemplo()

expect(funcionEjemplo).toHaveReturned()
/*...*/
```

- **toHaveLength()**: para comprobar que un objeto tiene una longitud concreta.

```javascript=
/*...*/
expect([1, 2, 3]).toHaveLength(3)
/*...*/
```

- **fireEvent**: dispara ciertos eventos dentro del DOM.
    - **fireEvent.change()**: se usa cuando esperamos que un elemento cambie su valor.
    ```javascript=
    /*...*/
    const inputEjemplo = getByLabelText(/Valor inicial/i);
    fireEvent.change(inputEjemplo, { target: { value: 'Nuevo valor' } });
    expect(inputEjemplo.value).toBe('Nuevo valor');
    /*...*/
    ```
    - **fireEvent.drop()**: se usa para simular eventos en los que hay que arrastrar y soltar elementos.
    ```javascript=
    test('MyComponent actualiza el orden de los elementos al soltar', () => {
      const { getByTestId } = render(<MyComponent />);
      const firstItem = getByTestId('item-1');
      const secondItem = getByTestId('item-2');
      fireEvent.dragStart(firstItem);
      fireEvent.drop(secondItem);
      expect(getByTestId('item-1')).toBe(secondItem);
      expect(getByTestId('item-2')).toBe(firstItem);
    });
    ```
    Debemos tener en cuenta que fireEvent.drop se debe usar con *fireEvent.dragStart* y *fireEvent.dragOver* para simular todo el proceso de arrastrar y soltar.
    
## Cheat Sheet
Existen tres tipos de búsquedas:

getBy / getAllBy: Busca de forma síncrona, Si no lo encuentra lanza una excepción
queryBy / queryAllBy: Busca de forma síncrona, Si no lo encuentra devuelve null
findBy / findAllBy: Busca los elementos de forma asíncrona. Si no lo encuentra lanza una excepción

--------------------------------

Una vez elegido el método de búsqueda, debemos usar un selector. Estos selectores vienen dados por testing library con un orden de prioridad en cuanto a cual aporta mayor accesibilidad a la web:

LabelText
Placeholder
Text
DisplayValue
AltText
Title
Role
TestId

Estos se combinan para crear la función:

Ej: getByText()

[Cheat sheet Test](https://testing-library.com/docs/react-testing-library/cheatsheet/)

## Enlaces útiles
[Testing playground](https://testing-playground.com/)

## Dudas
1. ¿Es bueno seleccionar un elemento por id o cualquier otro selector de CSS?
Es mejor basarse en otros aspectos, ya que si nos basamos en selectores CSS estamos acoplando nuestros tests a el html, y si en el futuro cambia un id, el tipo de elemento o lo que sea, se romperan los test que depende de ello.
Lo suyo es que cuando te basas en roles, haces los test mas resilientes a cambios, porque no ponen el foco en html, sino en lo que buscaria un usuario visualmente (rol=button, text=submit).
Aparte que indirectamente se mejora la accesibilidad.

### Out of Context
1. ¿Mejor uso de enums o interfaces para valores constantes?

## Vídeos de Apoyo
[Live Coding Componentes en React con TDD by *Adrián Ferrera*](https://youtu.be/iYhwoludFvg)
[Testing en React by *midudev*](https://youtu.be/KYjjtRgg_H0)
