Kodokon kodokon.com

Calidad: Testing Library, StrictMode, Profiler

Prueba el comportamiento en lugar de la implementación, aprovecha las dobles invocaciones de StrictMode y mide los renderizados con Profiler.

10 min · 3 preguntas

Abrir esta lección en Kodokon

La filosofía de Testing Library cabe en una frase: prueba lo que el usuario percibe, nunca los detalles de implementación. Concretamente, consultas el DOM a través del árbol de accesibilidad: getByRole primero (la reina de las consultas, que valida de paso tus roles ARIA), luego getByLabelText, getByText, y getByTestId solo como último recurso. Para las interacciones, prefiere user-event antes que fireEvent: donde fireEvent.change emite un evento aislado, user.type reproduce la secuencia completa (focus, keydown, keypress, input, keyup), exactamente como un teclado real. Las variantes findBy* combinan una consulta con una espera asíncrona: imprescindibles tras un paso de carga.

JSX
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('adds a task to the list', async () => {
  const user = userEvent.setup();
  render(<TodoApp />);

  await user.type(
    screen.getByRole('textbox'),
    'Review the report'
  );
  await user.click(
    screen.getByRole('button', { name: /add/i })
  );

  expect(
    await screen.findByText('Review the report')
  ).toBeInTheDocument();
});
Un test centrado en el comportamiento, no en la implementación.

StrictMode es una herramienta de desarrollo sin efecto alguno en producción. En desarrollo, llama a tus funciones de componente, a los inicializadores de estado y a las funciones pasadas a setState dos veces, para sacar a la luz los renderizados impuros: si dos ejecuciones dan resultados diferentes, tienes un efecto secundario oculto. Desde React 18, también hace pasar cada efecto por el ciclo montaje, desmontaje, remontaje: un efecto cuya limpieza no sea perfectamente simétrica (una suscripción nunca cancelada, una petición nunca abortada) se delata de inmediato. Es una preparación directa para funcionalidades en las que React desmonta y luego remonta una pantalla conservando su estado.

JSX
useEffect(() => {
  const controller = new AbortController();
  fetch('/api/user', { signal: controller.signal })
    .then((res) => res.json())
    .then(setUser)
    .catch(() => {});
  return () => controller.abort();
}, []);
Un efecto simétrico sobrevive al doble montaje de StrictMode.

Para medir, React proporciona el componente <Profiler> y su equivalente gráfico en las DevTools. Su callback onRender recibe, entre otras cosas, phase (mount, update, o nested-update), actualDuration (el tiempo realmente dedicado a renderizar el subárbol para este commit), y baseDuration (una estimación del tiempo para renderizar todo el subárbol sin ninguna memoización). La diferencia entre ambos mide la eficacia de tus memo y useMemo: un actualDuration muy por debajo de baseDuration significa que la memoización está trabajando a tu favor.

JSX
import { Profiler } from 'react';

function App() {
  return (
    <Profiler
      id="Sidebar"
      onRender={(id, phase, actualDuration) => {
        console.log(id, phase, actualDuration);
      }}
    >
      <Sidebar />
    </Profiler>
  );
}
Medir el coste real de renderizado de un subárbol.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Por qué StrictMode monta, desmonta y luego remonta cada efecto en desarrollo?
    • Para simular la latencia de red del servidor
    • Para comprobar que la limpieza de cada efecto es simétrica
    • Para vaciar la caché interna de React
    • Para forzar el batching de actualizaciones
  2. ¿A qué consulta de Testing Library deberías recurrir primero?
    • getByTestId, la más estable
    • getByClassName, la más precisa
    • getByRole, alineada con el árbol de accesibilidad
    • querySelector, la más flexible
  3. ¿Qué representa baseDuration en el callback del Profiler?
    • El tiempo del último commit únicamente
    • El tiempo de renderizado estimado del subárbol sin ninguna memoización
    • El tiempo transcurrido desde que el componente se montó
    • La duración media de los últimos diez renderizados