Pruebas de componentes en la práctica

Las pruebas de componentes son un buen punto de partida para comenzar a demostrar código de prueba práctico. Las pruebas de componentes son más sustanciales que las pruebas de unidades simples y menos complejas que pruebas de extremo a extremo y demuestran la interacción con el DOM. Más filosóficamente, el uso de React ha facilitado a los desarrolladores web el pensamiento de sitios web o aplicaciones web como componentes.

Por lo tanto, probar componentes individuales, independientemente de la complejidad que sean, es una buena para probar una aplicación nueva o existente.

En esta página, se explica cómo probar un componente pequeño con código externo complejo dependencias. Es fácil probar un componente que no interactúa con ningún otro código, como hacer clic en un botón y confirmar que un número aumenta. En realidad, muy poco código es así, y probar código que no tiene interacciones pueden tener un valor limitado.

(esto no pretende ser un instructivo completo, sino una sección posterior, “Pruebas automáticas) en la práctica, probaremos un sitio real con código de muestra que que puedes usar como instructivo. Sin embargo, esta página aún abarcará varios ejemplos de pruebas prácticas de componentes).

El componente que se está probando

Usaremos Vitest y su entorno de JSDOM para probar un componente de React. Esto permite ejecutamos rápidamente las pruebas con Node en la línea de comandos mientras emulamos un navegador.

Una lista de nombres con un
    Elige un botón junto a cada nombre.
Un componente de React pequeño que muestra una lista de usuarios de la red.

Este componente de React llamado UserList recupera una lista de usuarios de la red y te permite seleccionar uno de ellos. La lista de usuarios se obtiene a través de fetch dentro de una useEffect, y el controlador de selección se pasa Context Este es su código:

import React, { useEffect, useState, useContext } from 'react';
import { UserContext } from './UserContext.tsx';
import { UserRow } from './UserRow.tsx';

export function UserList({ count = 4 }: { count?: number }) {
  const [users, setUsers] = useState<any[]>([]);
  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users?_limit=' + count)
      .then((response) => response.json())
      .then((json) => setUsers(json));
  }, [count]);

  const c = useContext(UserContext);
  return (
    <div>
      <h2>Users</h2>
      <ul>
        {users.map((u) => (
          <li key={u.id}>
            <button onClick={() => c.userChosen(u.id)}>Choose</button>{' '}
            <UserRow u={u} />
          </li>
        ))}
      </ul>
    </div>
  );
}

Este ejemplo no demuestra las prácticas recomendadas de React (por ejemplo, usa fetch dentro de useEffect), pero es probable que la base de código contenga muchos casos. les gusta. Más precisamente, estos casos pueden parecer tercos a probar al principio. una mirada. Una sección futura de este curso hablará sobre cómo escribir código que se puede probar en detalle.

Aquí están los elementos que estamos probando en este ejemplo:

  • Verifica que se creen algunos DOM correctos en respuesta a los datos de la red.
  • Confirma que, cuando un usuario hace clic en él, se active una devolución de llamada.

Cada componente es diferente. ¿Por qué es interesante probar este tema?

  • Usa el fetch global para solicitar datos reales de la red, que podría ser inestable o lenta cuando se hace la prueba.
  • Importa otra clase, UserRow, que quizás no queramos probar de forma implícita.
  • Usa un Context que no forma parte específicamente del código que se está probando. por lo general, la proporciona un componente superior.

Escribe una prueba rápida para comenzar

Podemos probar rápidamente algo muy básico sobre este componente. Para ser claros, este este ejemplo no es muy útil. Pero es útil configurar el código estándar en un grupo llamado UserList.test.tsx (recuerda que los ejecutores de pruebas como Vitest, por De forma predeterminada, ejecuta archivos que terminan con .test.js o similares, incluido .tsx):

import { vi, test, assert, afterAll } from 'vitest';
import { render } from '@testing-library/react';
import { UserList } from './UserList.tsx';
import React, { ContextType } from 'react';

test('render', async () => {
  const c = render(<UserList />);

  const headingNode = await c.findAllByText(/Users);
  assert.isNotNull(headingNode);
});

Esta prueba afirma que, cuando se renderiza el componente, contiene el texto "Users". Funciona aunque el componente tenga un efecto secundario de enviar un fetch a la red. El fetch todavía está en progreso al final de la prueba, sin establecer extremo. No podemos confirmar que se muestra información del usuario cuando finaliza la prueba, al menos sin esperar un tiempo de espera.

fetch() de prueba

La simulación es el acto de reemplazar una función o clase real por algo el control para una prueba. Esta es una práctica común en casi todos los tipos de pruebas, excepto las pruebas de unidades más simples. Este tema se abordará más a fondo en Aserciones y otras primitivas.

Puedes simular fetch() para tu prueba, de modo que se complete rápidamente y muestre datos que esperas, y no "del mundo real" o desconocidos. fetch es una entidad global, lo que significa que no tenemos que import o require en nuestro código.

En Vitest, puedes simular un global llamando a vi.stubGlobal con un especial objeto que muestra vi.fn(): esto crea un modelo que podemos modificar más adelante. Estos se analizarán con más detalle en una sección posterior de este curso, pero puedes verlas en práctica en el siguiente código:

test('render', async () => {
  const fetchMock = vi.fn();
  fetchMock.mockReturnValue(
    Promise.resolve({
      json: () => Promise.resolve([{ name: 'Sam', id: 'sam' }]),
    }),
  );
  vi.stubGlobal('fetch', fetchMock);

  const c = render(<UserList />);

  const headingNode = await c.queryByText(/Users);
  assert.isNotNull(headingNode);

  await waitFor(async () => {
    const samNode = await c.queryByText(/Sam);
    assert.isNotNull(samNode);
  });
});

afterAll(() => {
  vi.unstubAllGlobals();
});

Este código agrega una simulación y describe una “falsa”. versión de la recuperación de red Response y espera a que aparezca. Si el texto no aparece, Para verificar esto, cambia la consulta en queryByText por un nombre nuevo: la prueba fallará.

En este ejemplo, se usaron los asistentes de simulación integrados de Vitest, pero los frameworks tienen enfoques similares a la simulación. Vitest es único en el sentido de que debes llama a vi.unstubAllGlobals() después de todas las pruebas o establece un valor global equivalente . Sin "deshacer" nuestro trabajo, la simulación de fetch puede afectar a otras pruebas y se responderán todas las solicitudes con nuestra extraña pila de JSON.

Importaciones de prueba

Quizás hayas notado que el componente UserList importa un componente llamada UserRow. Si bien no incluimos su código, puedes ver que renderiza el nombre del usuario: la prueba anterior busca "Sam", y ese no es se renderiza directamente dentro de UserList, por lo que debe provenir de UserRow.

Un diagrama de flujo de cómo
  de los usuarios se mueven a través de nuestro componente.
UserListTest no tiene visibilidad de UserRow.

Sin embargo, UserRow podría ser un componente complejo. Es posible que obtenga del usuario ni tener efectos secundarios que no son relevantes para nuestra prueba. Quitando eso de la variabilidad hará que las pruebas sean más útiles, especialmente cuando los componentes que quieren probar, se hacen más complejos y se entrelazan más con sus dependencias.

Puedes usar Vitest para simular ciertas importaciones, incluso si tu prueba no los usa directamente, de modo que cualquier código que los usa se proporciona con versión simple o conocida:

vi.mock('./UserRow.tsx', () => {
  return {
    UserRow(arg) {
      return <>{arg.u.name}</>;
    },
  }
});

test('render', async () => {
  // ...
});

Al igual que simular el fetch global, esta es una herramienta poderosa, pero puede convertirse insostenible si el código tiene muchas dependencias. Una vez más, la mejor solución para que consiste en escribir código que se puede probar.

Hacer clic y proporcionar contexto

React y otras bibliotecas como Lit, tienen un concepto llamado Context. El código de muestra incluye un UserContext que Invoca el método si se elige un usuario. Esto suele verse como una alternativa a "Desglose de prop", donde la devolución de llamada se pasa directamente a UserList.

El agente de prueba que escribimos no proporcionó UserContext. Agregando un clic acción a la prueba de React sin ella; esto, en el peor de los casos, generará una falla en la prueba o, si se proporciona una instancia predeterminada en otro lugar, causarás algún comportamiento nuestro control (similar a un UserRow desconocido mencionado anteriormente):

  const c = render(<UserList />);
  const chooseButton = await c.getByText(/Choose);
  chooseButton.click();

En cambio, cuando renderizas el componente, puedes proporcionar tu propio Context. Esta En este ejemplo, se usa una instancia de vi.fn(), una función de simulación de Vitest, que puede usarse. después del hecho para verificar que se hizo una llamada y los argumentos que se hicieron tus amigos. En nuestro caso, esto interactúa con el fetch simulado en la versión anterior ejemplo, y la prueba puede confirmar que el ID que se pasó era "sam":

  const userChosenFn = vi.fn();
  const ucForTest: ContextType<typeof UserContext> = { userChosen: userChosenFn as any };
  const c = render(
    <UserContext.Provider value={ucForTest}>
      <UserList />
    </UserContext.Provider>,
  );

  const chooseButton = await c.getByText(/Choose);
  chooseButton.click();
  assert.deepStrictEqual(userChosenFn.mock.calls, [['sam']]);

Se trata de un patrón simple pero poderoso que puede permitirte quitar dependencias del componente principal que intentas probar.

En resumen

Este fue un ejemplo rápido y simplificado que demuestra cómo crear un de componentes de React para probar y proteger un componente de React difícil de probar, centrarse en garantizar que el componente interactúe correctamente con su dependencias (el global fetch, un subcomponente importado y un Context)

Verifica tus conocimientos

¿Qué enfoques se utilizaron para probar el componente de React?

Simulación de dependencias complejas con dependencias simples para pruebas
Inserción de dependencias con Context
Globalización de stub
Verificar que un número aumentó