Testing

Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.

Por defecto, al utilizar test en una aplicación creada con create-react-app, se ejecutaran los tests correspondientes a ficheros que terminen en .test.jsx.

Funciones habituales en testing

Render

Recibe como parámetro el componente que queremos testear.

Screen

Se utiliza para buscar elementos en el dom renderizado.

Tipos de queries del objeto screen
  • get: accede a un elemento que debe existir en el momento de la invocación de este método. Si no lo encuentra, da error.
  • query: funciona igual que el get, pero si elemento buscado no existe, no da error. Por tanto, sirve para comprobar que algo no está.
test('Si el usuario vuelve a escribir después de un error, el mensaje se elimina', () => { 
    render(<Login />);
    const submitButton = screen.getByText("Enviar");
    const input = screen.getByPlaceholderText("Nombre usuario");

    userEvent.click(submitButton);
    userEvent.type(input, 'Hey'); // Escribimos un texto en el input

    const errorElement = screen.queryByText('Debes introducir un nombre de usuario');

    expect(errorElement).not.toBeInTheDocument();
});
  • find: devuelve una promesa que se resuelva cuando encuentra el elemento esperado. Si no lo encuentra, lanza un error. El tiempo de espera para ver si aparece el elemento es de un segundo.

Los tres aceptan expresiones regulares. La expresión regular /txt/i significa que buca cualquier texto que contenga la palabra txt sin tener en cuenta mayúsculas.

Acceso por id, class, etc

También podemos utilizar el id o el class de un elemento para acceder a él, pero en testing se pretende usar la información que el usuario ve, y evitar los accesos al DOM mediante aspectos técnicos como el id o el class. Igualmente, si quisieramos acceder a un elemento de esta forma, podríamos usar:

const { container } = render(<Login />);
const submitButton = container.querySelector('#id-del-boton');

Priorities del objeto screen

Son la segunda parte del método. Indican el tipo de elemento al que queremos acceder. Algunos de los más típicos:

  • ByText → Acceden en función de un texto escrito en el dom.
  • ByTestId → Si puedes encontrar el elemento de otra manera, es posible añadirle un identificador: data-testid=»container», por ejemplo.

Expect

Recibe como parámetro el elemento que queremos evaluar y a partir de él ejecutamos el método de la evaluación.

  • toBeInTheDocument();
  • toBe(2);
  • .toHaveAttribute(‘href’, ‘/login’);

fireEvent

Nos permite evaluar si sucede el comportamiento deseado cuando se detona un evento.

import userEvent from "@testing-library/user-event";
    
userEvent.type(emailInput, 'test@test.com'); // Escribe 1234 en el input
userEvent.click(submitButton);

La diferencia entre usar userEvent y usar fireEvent es que userEvent simula el flujo completo que haría un usuario en una interacción. Por ejemplo, al hacer click, se ejecutan los eventos mousedown, mouseup, mouseclick, etc. fireEvent sólo simula uno de esos eventos, en lugar del flujo completo de la interacción. Es recomentable usar userEvent siempre que se pueda.

Ejercicio Testing

Crea un componente Login de tal manera que validen los siguientes tests

Ejemplo del código de un test:

El siguiente test busca el botón de submit, lo pulsa y comprueba que aparece un mensaje de error (ya que no se ha rellenado el nombre ni la contraseña del usuario).

El comando test también hubiese funcionado si hubiesemos usado it en su lugar.

./components/Login/Login.test.js

import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Login from './Login';

test('Si el usuario pulsa el botón de envío si rellenar el campo nombre da error', () => { // Descripción del test
    // FASE 1: Arrange (preparación del test) 
    render(<Login />);
    const submitButton = screen.getByText("Enviar"); // Buscamos cualquier elemento que en su interior tenga el texto Enviar

    // FASE 2: Acting (acción que queremos testear) 
    userEvent.click(submitButton); // Este código ejecuta el evento click

    // FASE 3: Assert (comprobación de que el resultado es el esperado)
    const errorElement = screen.getByText('Debes introducir un nombre de usuario'); // Buscamos cualquier elemento que en su interior tenga el texto Debes introducir un nombre de usuario (mensaje de error)
    expect(errorElement).toBeInTheDocument(); // Si errorElement se encuentra en el documento, el test será correcto
});
import { useState } from "react";

const Login = (props) => {
  const [name, setName] = useState('');
  const [isError, setIsError] = useState(false);
  return (

    <div >
      <input onChange={e => setName(e.target.value)} />
      <p>{name}</p>
      {
        isError && <p>Debes introducir un nombre de usuario</p>
      }
      <button onClick={() => {
        setIsError(name === '')
      }}>Enviar</button>
    </div>
  );
}
export default Login;

Integrando testing con React Router DOM

Si utilizamos Router, Redux, Context API o cualquier otro provider que deba envolver a nuestro código, tendremos que envolver el componente con el que estamos haciendo testing en un Wrapper que utilice el Provider.

import { MemoryRouter } from 'react-router-dom';
...
test('Si el usuario pulsa el botón de envío si rellenar el campo nombre da error', () => { // Descripción del test
    // FASE 1: Arrange (preparación del test) 
    render(<MemoryRouter><Login /></MemoryRouter>);

Para no tener que cargar los providers necesarios constantemente, podemos definirlos en un fichero externo en el que los utilicemos. Luego, usaríamos el método render que hemos definido en este fichero externo.

./src/helpers/test-utils.js
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router-dom';

const customRender = (ui) => {
    const Wrapper = ({ children }) => (
        <MemoryRouter>
            {children}
        </MemoryRouter>
    );
    return render(ui, { wrapper: Wrapper });
};

export * from '@testing-library/react';
export { customRender as render, userEvent };
import { render, userEvent } from '../helpers/test-utils';
...
test('Si el usuario pulsa el botón de envío si rellenar el campo nombre da error', () => { // Descripción del test
    render(<Login />);

describe

El comando describe nos permite establecer agrupaciones de varios tests para que cuando los veamos en la terminal los veamos agrupados.

describe('home page', () => {
  test('must display a title for loggin', () => {
    render(<Home />);
    expect(screen.queryByText(/Star Wars Ships Wik/i)).toBeInTheDocument();
  });
});

Ejecución de los tests

En el caso de que estemos usando create-react-app, ejecutaremos:

npm run test

Por 9.99€ al mes tendrás acceso completo a todos los cursos. Sin matrícula ni permanencia.