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.
Los MockServer son herramientas que nos permiten simular respuestas HTTP, evitando la necesidad de configurar un aplicativo original para consumir los recursos necesarios.
Ambientar los aplicativos localmente a veces llevan su tiempo, ya que puede involucrar desde temas técnicos como de negocio.
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.
1. Descargar https://mockoon.com/download/#download-section
Se nos mostrará un demo donde se resaltan tres secciones importantes.
3. Creamos nuestro ambiente local.
npx create-react-app my-app cd my-app npm start
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: });
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.
src/CreateOrder.js
1: const CreateOrder = () => {
2: return null
3: }
4:
5: export default CreateOrder
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.
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”.
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.
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?
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
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: }
…
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