Cómo usar un MockServer en mi aplicación React

Gabriel Jiménez | Hace más de 1 año

Simular una API puede ahorrarte horas de configuración y bloqueos innecesarios. En este artículo verás cómo usar Mockoon como Mock Server para desarrollar en React sin depender del backend, replicando reglas de negocio y validando el comportamiento de tu app con pruebas.

Como usar un MockServer en mi aplicación React

Los MockServer son herramientas que nos permiten simular respuestas HTTP, evitando la necesidad de configurar un aplicativo original para consumir los recursos necesarios.

¿Donde sería útil usar un MockServer?


  • En desarrollos tempranos donde tener un API operable puede tardar semanas o meses.
  • Si el aplicativo a consumir es demasiado complejo para ambientarlo localmente.
  • En arquitecturas de microservicios. Debido a que muchos aplicativos interactúan entre si para un solo objetivo, puede ser necesario ambientar más de uno localmente, lo cual es costoso muchas de las veces.

Mockserver vs Aplicativo original


¿Por qué ambientar el original suele ser costoso?

Ambientar los aplicativos localmente a veces llevan su tiempo, ya que puede involucrar desde temas técnicos como de negocio.


Costos técnicos vs costos de negocio

Para el caso técnico involucra desde configuración de dependencias, configuración de permisos, creación de usuarios, configuración de base de datos, etc. Por otro lado, en temas de negocio debemos de conocer las reglas de negocio, por ejemplo, si un recurso se encuentra en tal estado es porque debe de tener un estatus x y que ademas este asociado a otro recurso y que ese recurso deba de tener valores no nulos en tres de sus propiedades.

Original APP vs MockServer


Todo esto hace que ambientar una aplicación localmente sea costoso. Sin embargo, no quiero decir que no sea importante conocer los temas técnicos y de negocio, solo debería ser abordado gradualmente.
Teniendo un poco más de contexto sobre los MockServer saltemos a un ejemplo práctico.

Configuración de MockServer


Herramienta que usaremos

Para este ejemplo, vamos a utilizar Mockoon https://mockoon.com/ como MockServer.


Generalmente, los MockServer tienen funcionalidades similares. Esto hace que aprender uno facilite aprender los otros.


Pasos para configurar Mockoon

Veamos como ponerlo en acción.


1. Descargar https://mockoon.com/download/#download-section


Descargar Mockoon

2. Ejecutar el aplicativo
Secciones destacadas de Mockoon


Entendiendo la interfaz

Se nos mostrará un demo donde se resaltan tres secciones importantes.

  1. Rojo. Listado de ambientes locales.
  2. Rosa. Listado de routes.
  3. Blanco. Configuración de una route.

3. Creamos nuestro ambiente local.

Crear un ambiente local y el endpoint /orders

Para este ejemplo, vamos a crear un nuevo ambiente local el cual va a simular un microservicio de pagos, escuchando en el puerto 8001 y con un endpoint /orders de tipo GET que regresa un array de órdenes.

Creación de ambiente local en Mockoon


Al hacer clic, nos generara un archivo .json, este archivo almacena la configuración de todo lo que hagamos dentro de la interfaz gráfica.

4. Creamos y configuramos nuestra route orders.

Creación de route orders en Mockoon


5. Configuración de puerto

Configuración de puerto en Mockoon


6. Encendemos aplicativo local

Encender aplicativo local


7. Consultamos el endpoint.

Respuesta de endpoint orders


En cuestión de minutos habilitamos un MockServer listo para usarse y nos evitamos dolores de cabeza en ambientar el aplicativo original.
Ahora bien, nuestro siguiente paso es usarlo. Para esto, vamos a imaginar el siguiente caso de uso.

Caso práctico utilizando MockServer y React


Requerimiento

Necesitamos un formulario que permita seleccionar una orden, insertar un nombre de cliente. En caso de que la orden ya se haya asociado a un cliente, mostrar un mensaje de error.

Interfaz para crear orden


Paso 1. Crear el proyecto

npx create-react-app my-app
cd my-app
npm start

Paso 2. Ataquemos el problema con pruebas

Lo primero es crear nuestro archivo de prueba.

src/CreateOrder.test.js

    1: describe('CreateOrder', () => {
    2:   it('crear orden correctamente', () => {
    3: 
    4:   });
    5: 
    6:   it('si la orden ya fue asignada mostrar mensaje de error', () => {
    7: 
    8:   });
    9: });

Paso 3. Resolver “crear orden correctamente”

src/CreateOrder.test.js

…
    5:   it('crear orden correctamente', async () => {
    6:     let props = {
    7:       ordersPost: jest.fn(),
    8:       ordersGet: jest.fn().mockResolvedValue([{ id: 1, name: "Order 1" }])
    9:     }
   10: 
   11:     await waitFor(() => {
   12:       render(
   13:         <CreateOrder { ...props }/>
   14:       )
   15:     })
   16: 
   17:     fireEvent.change(screen.getByTestId('customer'), { target: { value: 'Gabriel' } });
   18:     fireEvent.change(screen.getByTestId('order', { target: { value: 1 } }));
   19: 
   20:     fireEvent.submit(screen.getByText('Guardar'))
   21: 
   22:     expect(props.ordersPost).toHaveBeenCalledWith({
   23:       customer: 'Gabriel',
   24:       order: "1"
   25:     })
   26:   });
…


Analicemos


Línea 6..9. Definimos los props que componente requiere.

Línea 11..15. Montamos el componente con sus props.

Línea 17..18. Asignamos valores a los campo cliente y orden.

Línea 20. Hacemos clic en el botón guardar.Línea 22..25. Validamos que la función onSubmit se llame correctamente con los parámetros.


Pasos 4–8. Hacer pasar la prueba


Componente CreateOrder no definido


Como era de esperar, nos arrojo un error. El componente CreateOrder no esta definido.
src/CreateOrder.js

    1: const CreateOrder = () => {
    2:   return null
    3: }
    4: 
    5: export default CreateOrder

Elemento con propiedad data-testid="customer" no definido


😭 Otro error. No esta encontrado el elemento con la propiedad data-testid=“customer” y esta en lo correcto, porque el componente esta vació. Solucionemos esto.

    1: import * as React from 'react';
    2: 
    3: const { useState, useEffect } = React
    4: 
    5: const CreateOrder = ({ ordersPost, ordersGet }) => {
    6:   const [data, setData] = useState(null)
    7:   const [orders, setOrders] = useState([])
    8: 
    9:   useEffect(() => {
   10:     ordersGet().then((items) => setOrders(items))
   11:   }, [])
   12: 
   13:   function handleChange({ target }) {
   14:     setData({
   15:       ...data,
   16:       [target.name]: target.value,
   17:     })
   18:   }
   19: 
   20:   async function handleSubmit() {
   21:     await ordersPost(data)
   22:   }
   23: 
   24:   return(
   25:     <form onSubmit={handleSubmit}>
   26:       <div>
   27:         <label htmlFor="customer">Cliente</label>
   28:         <input
   29:           type="text"
   30:           name='customer'
   31:           id='customer'
   32:           data-testid='customer'
   33:           onChange={handleChange}
   34:         />
   35:       </div>
   36:       <div>
   37:         <label htmlFor="order">Órdenes</label>
   38:         <select
   39:           name="order"
   40:           id="order"
   41:           data-testid='order'
   42:           onChange={handleChange}
   43:         >
   44:           {
   45:             orders.map(order => (
   46:               <option key={order.id} value={order.id}>{order.name}</option>
   47:             ))
   48:           }
   49:         </select>
   50:       </div>
   51:       <button type='submit'>Guardar</button>
   52:     </form>
   53:   )
   54: }
   55: 
   56: export default CreateOrder


Analicemos.


Línea 6..7. Definimos dos estados, uno para almacenar los valores que el usuario ingresa y otro para listar las órdenes.

Línea 9..11. Obtenemos y asignamos las órdenes.

Línea 13..18. Función para asignar los valores que ingresa el usuario al estado data.

Línea 20..22. Función para guardar una orden.

Línea 26.35. Campo cliente

Línea 36..50. Campo de selección de órdenes.

Línea 51. Botón de guardado. 


Prueba "crear orden correctamente" exitosa


🎉 Felicidades, hemos logrado pasar nuestra primera prueba.

Paso 9. Resolver “si la orden ya fue asignada mostrar mensaje de error”.


src/CreateOrder.test.js

…
   28:   it('si la orden ya fue asignada mostrar mensaje de error', async () => {
   29:     let props = {
   30:       ordersPost: jest.fn().mockRejectedValue({ message: "La orden ya fue asignada al cliente Gabriel" }),
   31:       ordersGet: jest.fn().mockResolvedValue([{ id: 1, name: "Order 1" }])
   32:     }
   33: 
   34:     await waitFor(() => {
   35:       render(
   36:         <CreateOrder { ...props }/>
   37:       )
   38:     })
   39: 
   40:     fireEvent.change(screen.getByTestId('customer'), { target: { value: 'Gabriel' } });
   41:     fireEvent.change(screen.getByTestId('order', { target: { value: 1 } }));
   42: 
   43:     await act(() => {
   44:       fireEvent.submit(screen.getByText('Guardar'))
   45:     })
   46: 
   47:     expect(document.body.textContent).toContain(`La orden ya fue asignada al cliente Gabriel`)
   48:   });


Analicemos.


Línea 29..32. Definimos los props que componente requiere. Sin embargo, en el ordersPost ahora nos esta regresando un error.

Línea 34..38. Montamos el componente con sus props.

Línea 40..41. Asignamos valores a los campo cliente y orden.

Línea 43..45. Hacemos clic en el botón guardar.

Línea 47. Validamos que en el componente contenga el texto “La orden ya fue asignada al cliente Gabriel”.


El texto "La orden ya fue asignada al cliente Gabriel" no se ha encontrado


Ups, parece que el texto no fue encontrado.
src/CreateOrder.js

…
    6:   const [data, setData] = useState(null)
    7:   const [orders, setOrders] = useState([])
    8:   const [error, setError] = useState(null)
…
   21:   async function handleSubmit() {
   22:     try {
   23:       await ordersPost(data)
   24:     } catch (e) {
   25:       setError(e)
   26:     }
   27:   }
…
   56:       {
   57:         error && <p>{error.message}</p>
   58:       }
   59:       <button type='submit'>Guardar</button>
…


Analicemos


Línea 8. Estado para almacenar el error.

Línea 21..27. En caso de error, guardamos el error.

Línea 57. Mostramos el error en caso de estar definido.


Prueba "Si la orden ya fue asignada mostrar mensaje de error" exitosa


🤞🏼Lo logramos, nuestras pruebas pasaron correctamente. Sin embargo, si levantamos el proyecto no podremos ver el comportamiento esperado de las pruebas porque no hemos definido los endpoints que recibe como prop nuestro componente.

Integración con el MockServer

Aquí es donde entra en acción nuestro MockServer que acabamos de crear. Al inicio creamos nuestro primer endpoint /orders que nos va a servir para listar las órdenes como en la prueba. Pero que sucede con el caso de si existe un usuario asociado a una orden, ¿cómo podemos replicarlo? ¿Existe alguna funcionalidad en nuestro MockServer que nos ayude?


Reglas condicionales en Mockoon (responses por condición)

Los MockServers nos permiten definir respuesta con base en ciertas condiciones. Veamos como lograrlo.

Definimos una ruta encargada de regresar un estatus ok cuando ingresemos un nombre diferente a Gabriel.

Route POST exitosa cuando nombre es diferente a Gabriel


Definición de rule de ROUTE POST exitosa


Definamos una ruta encargada de regresar un estatus failed cuando mandemos el valor Gabriel.

Route POST fallida cuando nombre es igual a Gabriel


Definición de rule de ROUTE POST fallida


Montar el componente en App


src/App.js

    1: import CreateOrder from "./CreateOrder";
    2: 
    3: const ordersGet = () =>
    4:   fetch('http://localhost:8001/orders')
    5:     .then(res => res.json());
    6: 
    7: const ordersPost = (body) =>
    8:   fetch('http://localhost:8001/orders',
    9:     { method: 'POST', body: JSON.stringify(body), headers: { 'Content-Type': 'application/json' } })
   10:     .then((res) => {
   11:       if(!res.ok) {
   12:         return res.json().then(e => {
   13:           const error = new Error();
   14:           error.message = e.message;
   15:           throw error;
   16:         });
   17:       }
   18:     })
   19: 
   20: function App() {
   21:   return <CreateOrder {...{ordersGet, ordersPost} } />;
   22: }
   23: 
   24: export default App;


Analicemos.


Línea 3..5. Endpoint para traer la información de las órdenes

Línea 7..18. Endpoint para simular el guardado de nuestra orden.

Línea 20..22.  Componente App


Levantamos nuestra aplicación frontend así como nuestro MockServer


Al iniciar nuestro frontend vemos que nos esta regresando la data de nuestro MockServer y pinta las órdenes correctamente.

Consumiendo mockoon desde nuestra frontend APP


Ahora guardemos una orden con un cliente con una orden asociada. Nos debería de arrojar un error como lo vimos en en nuestras pruebas.

Problema al guardar formulario


Detalle importante: prevenir el submit “real” del form

Ups, creo que hubo un error. Esto sucede porque el <form> tiene dos propiedades action y method y no hemos definido ningún valor, entonces, como action se esta tomando la página actual y en el method por defecto toma el get, es por eso que vemos la url localhost:3000/?customer=Gabriel&order=1.

Para evitar esto, hay que prevenir este comportamiento.


Al ejecutar nuestras pruebas en un ambiente no real puede ocasionar este tipo de problemas. Sin embargo, te aseguro que la cantidad de errores será menor.

src/CreateOrder.js

…
   21:   async function handleSubmit(event) {
   22:     event.preventDefault()
   23: 
   24:     try {
   25:       await ordersPost(data)
   26:     } catch (e) {
   27:       setError(e)
   28:     }
   29:   }
…

Listo, ejecutemos de nuevo.

Respuesta exitosa de cliente igual a Gabriel


Podemos ver que el MockServer esta respondiendo conforme al paso 12.

Por último, vamos a crear una orden con un cliente sin una orden asociada.

Respuesta exitosa de cliente diferente a Gabriel


Perfecto, nuestro MockServer esta respetando las reglas que definimos. 

Conclusión


Los MockServers son herramientas poderosas que nos permiten replicar comportamientos esperados basados en diferentes reglas de negocio, como vimos en nuestro ejemplo.


Además, las pruebas nos permiten comenzar a desarrollar la lógica sin tener ni quiera un ambiente local original o un MockServer.


Evitar distracciones que no nos competen dentro de nuestro rol, nos permitirá enfocarnos y desarrollar aplicaciones más escalables.

Si quieres aprender otras estrategias para probar tus componentes React. Visita mi hub de Testing en React.

Hub de Testing en React