Cómo usar Mock Service Worker para simular las llamadas a tu API

Gabriel Jiménez | Hace alrededor de 1 mes

En este artículo, aprenderemos a simular peticiones al backend dentro de nuestras pruebas, usando Mock Service Worker. Además, listaremos las diferencias entre Jest, React Testing Library y Mock Service Worker. Una vez que entendamos para que sirve cada herramienta, desarrollaremos un ejemplo práctico utilizándolas en conjunto.


Qué es Mock Service Worker

Mock Service Worker es una librería para simular peticiones a nuestra API.


En empresas medianas o grandes, configurar el API puede ser una tarea bastante pesada, puede que nuestra API involucre que varios microservicios se comuniquen entre si. Además, cada uno puede tener su propia configuración. Como desarrolladores frontend configurar estos servicios, puedo atrasarnos y distraernos de nuestro objetivo principal — comunicar el frontend con nuestros servicios (backend).


Para que nos quede más claro, veamos el diagrama (figura 1).

Figura 1. Cómo funciona Mock Service Worker


Lo que está sucediendo:
  1. El usuario hace una acción (por ejemplo, “Pedir pizza”).
  2. La app envía la petición HTTP.
  3. MSW la intercepta y revisa si existe un handler.
  4. Si hay handler, responde con datos simulados.
  5. Si no hay, deja pasar la petición al backend real.
  6. También puede simular errores para probar casos específicos.

Un punto importante a destacar sobre Mock Service Worker es sobre la respuesta que devuelve al interceptar una petición HTTP. Si la petición que hacemos “api/productos” existe en Mock Service Worker, devuelve la data simulada, en caso contrario, nuestra API real la procesa.


Diferencia entre Jest, React Testing Library y Mock Service Worker

Para diferenciar cada una de las herramientas, veamos el siguiente diagrama (figura 2).

Figura 2. Diferencia entre Jest, React Testing Library y Mock Service Worker


De forma muy general, Jest es el encargado de ejecutar y validar el resultado esperado. Mientras React Testing Library Renderiza el componente y Actualiza la interfaz gráfica. Por último, Mock Service Worker intercepta las peticiones a nuestro backend para devolver data simulada.
Si quieres saber más sobre React Testing Library y Jest, revisa los siguiente artículos:
Qué es Jest y cómo funciona con React
Qué es React Testing Library y cómo funciona con Jest

Cómo configurar Mock Service Worker con Jest y React Testing Library

Para configurar Mock Service Worker es bastante sencillo, consta de cuatro sencillos pasos:

1. Instalar Mock Service Worker usando “npm”

npm install [email protected] --save-de


2. Crear nuestros interceptores (handlers) para simular la data

import { rest } from "msw";

export const handlers = [
  rest.get(“/api/usuarios”, (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json([{ id: 1, name: "John Doe" }])
    );
  }),
];


3. Configuramos un server para habilitar los interceptores de nuestra aplicación

import { setupServer } from "msw/node";
import { handlers } from "./handlers";

export const server = setupServer(...handlers);


4. Encendemos el server para interceptar cualquier petición HTTP

import { server } from ‘./mocks/node.js'
server.listen()


De esta forma, cada que se haga una petición a la url “/api/usuarios”, Mock Service Worker entra en acción.


Si quieres aprender a configurar Jest en tu entorno, revisa mi publicación: Qué es Jest y cómo funciona con React


Ejemplo práctico usando Jest, React Testing Library y Mock Service Worker

Nuestro ejemplo práctico, consiste en crear una aplicación bastante sencilla para ilustrar el funcionamiento de Jest, React Testing Library y Mock Service Worker.


Nuestra aplicación hace lo siguiente:


  • Permite realizar una compra de un producto.
  • Una compra cosiste de tres campos: precio, producto y descripción.
  • Los productos se obtienen de un API y se listan en un select.

Nuestra prueba debe de validar lo siguiente:


  • Qué el backend reciba la data con el formato correcto.
  • Qué los productos se listen correctamente.

Configurando nuestro entorno

Para agilizar y enfocarnos en la funcionalidad principal, utilizaremos Create React App porque facilita la configuración de Jest y React Testing Library.


Creación de nuestro proyecto

Creamos nuestra aplicación usando el comando:

npx create-react-app mock-service-worker
cd mock-service-worker
npm start


Instalación y configuración de Mock Service Worker

Ya dentro del proyecto “my-app”, instalamos Mock Service Worker con el siguiente comando:

npm install [email protected] --save-de


Creación de nuestro interceptor de productos

File: src/mocks/handlers.js
    1: import { rest } from "msw";
    2: 
    3: export const handlers = [
    4:   rest.get("/api/productos", (req, res, ctx) => {
    5:   return res(
    6:     ctx.status(200),
    7:     ctx.json([{ id: 1, name: "Producto A" }])
    8:   )})
    9: ]


Habilitamos nuestros interceptores

File: src/mocks/server.js
    1: import { setupServer } from 'msw/node'
    2: import { handlers } from './handlers'
    3: export const server = setupServer(...handlers)

Implementación de pruebas de nuestro ejemplo

Ya que hemos configurado todo nuestro entorno, estamos listo para atacar la funcionalidad principal de nuestra aplicación “Crear una compra”.


Lo primero es crear nuestro archivo de pruebas “CrearCompra.test.jsx”, y dentro, nuestra primera prueba.

File: src/CrearCompra.test.jsx
    1: describe('CrearCompra', () => {
    2:   it('crear compra correctamente', () => {
    3:   })
    4: });


Si quieres aprender más sobre testing en React, revisa alguno de mis casos prácticos como: Cómo probar el flujo de registro en Slack con React Testing Library y Jest


Continuemos con la implementación de nuestra prueba:

File: src/CrearCompra.test.jsx
    1: import * as React from 'react';
    2: import CrearCompra from "./CrearCompra";
    3: import {act, fireEvent, render, screen} from "@testing-library/react";
    4: import {server} from "./mocks/server.js";
    5: 
    6: describe('CrearCompra', () => {
    7:   it('crear compra correctamente', async () => {
    8:     server.listen()
    9: 
   10:     const props = { onSubmit: jest.fn() }
   11: 
   12:     await act(() => {
   13:       render(<CrearCompra { ...props } />);
   14:     })
   15: 
   16:     fireEvent.change(screen.getByTestId('producto'), { target: { value: 1 } });
   17:     fireEvent.change(screen.getByPlaceholderText('Precio'), { target: { value: '1000' } });
   18:     fireEvent.change(screen.getByPlaceholderText('Descripción'), { target: { value: 'Para mi mascota' } });
   19: 
   20:     fireEvent.submit(screen.getByText('Guardar'));
   21: 
   22:     expect(props.onSubmit).toHaveBeenCalledWith({
   23:       producto: '1',
   24:       precio: '1000',
   25:       descripcion: 'Para mi mascota',
   26:     });
   27:   })
   28: });


Línea 8

Activamos nuestro servidor para interceptar las peticiones a nuestra API.


Línea 10

Definimos los props del componente


Linea 12-14

Montamos nuestro componente mientras esperamos a que los productos se devuelvan del API y se los listamos al usuario.


Línea 16

Seleccionamos el producto con “id” 1.


Línea 17-18

Escribimos el precio y una descripción.


Línea 20

Hacemos clic en el botón Guardar. En este momento se lanza la petición al backend.


Line 22-27

Validamos que la función onSubmit reciba la data correctamente. 


Ahora, debemos de agregar la implementación del componente productivo para pasar esta prueba.

File: src/CrearCompra.jsx
    1: import React from 'react'
    2: import axios from 'axios'
    3: import {useEffect, useState} from "react";
    4: 
    5: const CrearCompra = ({ onSubmit }) => {
    6:   const [values, setValues] = useState({
    7:     producto: "",
    8:     precio: "",
    9:     descripcion: "",
   10:   });
   11:   const [productos, setProductos] = useState([]);
   12: 
   13:   useEffect(() => {
   14:     axios.get("/api/productos")
   15:       .then(({ data }) => {
   16:         setProductos(data)
   17:       })
   18:   }, [])
   19: 
   20:   function handleChange({ target }) {
   21:     const { name, value } = target;
   22: 
   23:     setValues({ ...values, [name]: value });
   24:   }
   25: 
   26:   async function handleSubmit() {
   27:     await onSubmit(values)
   28:   }
   29: 
   30:   return (
   31:     <form onSubmit={handleSubmit}>
   32:       <select name="producto" data-testid="producto" onChange={handleChange}>
   33:         <option value="">Seleccione</option>
   34:         {productos.map(producto => (
   35:           <option key={producto.id} value={producto.id}>{producto.name}</option>
   36:         ))}
   37:       </select>
   38:       <input type="text" name='precio' placeholder="Precio" onChange={handleChange} />
   39:       <input type="text" name='descripcion' placeholder="Descripción" onChange={handleChange} />
   40:       <button type="submit">Guardar</button>
   41:     </form>
   42:   )
   43: }
   44: 
   45: export default CrearCompra


Bastante sencillo, define un estado con tres campos: producto, precio y descripción. Cada campo se sincroniza usando handleChange. Al montar el componente realizamos una petición al API para solicitar los productos y poderlos listar. Al hacer clic en el botón se envía la data al backend mediante la función onSubmit.


Si ejecutamos esta prueba, obtendremos un resultado exitoso como el siguiente:


  PASS  src/CrearCompra.test.jsx
  CrearCompra
    ✓ crear compra correctamente (34 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.717 s, estimated 1 s

Conclusión

Mock Server Worker es una herramienta útil para simular la comunicación con nuestras API. Evita tener que preocuparnos por configurar y mantener nuestras APIs funcionando en nuestro entorno local.


Si quieres aprender más sobre pruebas, te invito a que revises mi HUB sobre testing en react, donde aprenderás todo lo necesario para involucrarte en el mundo de las pruebas. Una vez dentro, te aseguro, no vas a querer salir :).