Publicación: 5 months

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

Ambientar los aplicativos localmente a veces llevan su tiempo, ya que puede involucrar desde temas técnicos como 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.

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.

Veamos como ponerlo en acción.

Descargar Mockoon

2. Ejecutar el aplicativo

Secciones destacadas de Mockoon


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.

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.

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. Comencemos creando nuestro proyecto.

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

Paso 2. Ataquemos el problema desde un enfoque de 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. Resolvamos la primera prueba “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.

Paso 4. Ejecutemos nuestra prueba.

Componente CreateOrder no definido


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

Paso 5. Creemos el componente.

src/CreateOrder.js

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

Paso 6. Ejecutemos de nuevo la prueba.

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.

Paso 7. Implementemos la solución para la prueba “crear orden correctamente”.

    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. 

Paso 8. Ejecutemos de nuevo nuestra prueba.

Prueba "crear orden correctamente" exitosa


🎉 Felicidades, hemos logrado pasar nuestra primera prueba.

Paso 9. Resolvamos la última prueba “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”.

Paso 10. Ejecutemos nuestra prueba.

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


Ups, parece que el texto no fue encontrado.

Paso 11. Modifiquemos nuestro componente para pasar nuestra prueba.

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.

Paso 12. Ejecutemos la prueba.

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.

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?

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

Paso 11. 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


Paso 12. 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


Paso 13. Montamos nuestro componente en la raíz del proyecto.

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

Paso 14. 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


Paso 15. 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


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.

Paso 16. 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.