Gabriel Jiménez | Hace alrededor de 1 año
En el post anterior: React Testing (Parte 1): Caso práctico con el registro de usuario en Twitch, vimos como hacer nuestra primera prueba que consistía en crear una cuenta correctamente. En esta ocasión, vamos a atacar una prueba que valide que un usuario no pueda estar repetido.
Antes de saltar al código analicemos tres posibles soluciones.
La primera y más obvia sería ambientar el backend localmente y crear el usuario. Sin embargo, esto puede ser un proceso engorroso y en ocasiones frustrante. ¿Por qué? Twitch tiene una gran cantidad de usuarios y funcionalidades, puede ser que los equipos esten divididos y asignados a servicios específicos. Estos servicios pueden ser microservicios u otro tipo de aplicativo.
Imaginemos en el mejor de los casos, solo necesitar 4 aplicativos para crear un usuario. Al inicio pueden parecer pocos, pero no hemos considerado que cada aplicativo tiene sus propias dependencias, configuraciones, versiones de framework y lenguaje, entre otras cosas. Esto puede llevar un par de horas hasta días.
La segunda solución, sería usar un MockServer para simular la comunicación con el backend. Esta solución puede ser más rápida que la primera solución, ya que solo necesitamos inicializar un aplicativo pero aun así, debemos saber elegir entre usar uno de terceros o crear uno propio. De igual manera pueden ser horas de aprendizaje o días.
La última solución, es realizar una prueba donde simularemos directamente en el código una respuesta del backend. Al hacer esto, evitamos muchos de los problemas de las soluciones anteriores.
Dicho esto, saltemos a tirar código.
Lo primero que haremos es definir el nombre de la prueba
1: import {fireEvent, render, screen} from "@testing-library/react"; 2: import CreateTwitchAccount from "./CreateTwitchAccount"; … 28: 29: it('si el usuario se repite mostrar mensaje de error', () => { 30: 31: }); 32: });
Como ven el nombre es bastante claro sobre el propósito de la prueba. Recomiendo en el mayor de los casos nombrar las pruebas como si fuéramos nosotros el usuario final y evitar usar términos técnicos.
Antes de continuar con la implementación de la prueba pensemos en el flujo.
Ahora que ya sabemos el flujo, veamos la implementación
29: it('si el usuario se repite mostrar mensaje de error', async () => { 30: const props = { 31: createAccount: jest.fn() 32: } 33: 34: render( 35: <CreateTwitchAccount {...props} /> 36: ) 37: 38: fireEvent.change(screen.getByTestId('username'), { target: { value: 'Gabriel' } }) 39: 40: expect(document.body.textContent).toContain("El usuario Gabriel ya existe") 41: });
Analicemos
Línea 30..32. Definimos los props que el componente necesita
Línea 34..36. Montamos el componente y pasamos los props
Línea 38. Asignamos el valor “Gabriel” al campo Usuario
Línea 40. Validamos que el texto se encuentre dentro del componente
Un error en ocasiones es visto como algo muy malo aunque en ciertas ocasiones es lo contrario, una oportunidad para detectar flujos alternos que no se habían contemplado. Dicho esto, implementemos la solución para esta prueba.
src/CreateTwitchAccount.test.js … 29: it('si el usuario se repite mostrar mensaje de error', async () => { 30: const props = { 31: createAccount: jest.fn(), 32: users: jest.fn() 33: } 34: 35: props.users.mockRejectedValue({ 36: errors: ["El usuario Gabriel ya existe"] 37: }) 38: 39: render( 40: <CreateTwitchAccount {...props} /> 41: ) 42: 43: await act(() => { 44: fireEvent.change(screen.getByTestId('username'), { target: { value: 'Gabriel' } }) 45: }) 46: 47: expect(document.body.textContent).toContain("El usuario Gabriel ya existe") 48: });
Analicemos
Línea 32. Prop encargado de simular el endpoint donde validamos los usuarios existentes
Línea 35..37. Simulamos la respuesta del endpoint
Línea 43..45. Asignamos el valor “Gabriel” al campo usuario y esperamos hasta obtener la respuesta del endpoint
1: import {useState} from "react"; 2: 3: const CreateTwitchAccount = ({ createAccount, users }) => { 4: const [values, setValues] = useState({}) 5: const [errors, setErrors] = useState([]) 6: 7: function onChange({ target }) { 8: values[target.name] = target.value 9: setValues({ ...values }) 10: } 11: 12: function handleSubmit(event) { 13: event.preventDefault() 14: 15: if(errors.length !== 0) { 16: return 17: } 18: 19: createAccount({ ...values }) 20: } 21: 22: async function handleUsernameChange(event) { 23: const { username } = event.target 24: try { 25: await users(username) 26: setErrors([]) 27: } catch(e) { 28: setErrors(e.errors) 29: } finally { 30: onChange(event) 31: } 32: } 33: 34: return( 35: <form onSubmit={handleSubmit}> 36: <ul> 37: {errors && errors.map((error, index) => <li key={index}>{error}</li>)} 38: </ul> 39: <input type="text" name="username" 40: data-testid="username" placeholder="Usuario" onChange={handleUsernameChange} /> 41: <input type="text" name="password" 42: data-testid="password" placeholder="Password" onChange={onChange} /> 43: <input type="text" name="birthdate" 44: data-testid="birthdate" placeholder="Fecha de nacimiento" onChange={onChange} /> 45: <input type="text" name="email" 46: data-testid="email" placeholder="Correo electrónico" onChange={onChange} /> 47: <button type="submit" disabled={errors.length !== 0}>Guardar</button> 48: </form> 49: ) 50: } 51: 52: export default CreateTwitchAccount
Ahora vemos los cambios en el componente
Línea 5. Definimos un estado para almacenar los errores
Línea 15..17. Si existen errores no permitimos crear el usuario
Línea 22..32 Función que valida si el usuario ingresado existe
Línea 36..38. Mostramos los errores devueltos por el endpoint users
Línea 40. Se asocia la función handleUsernameChange al campo usuarioLínea 47. Deshabilitamos el botón si existen errores
20: async function handleUsernameChange(event) { 21: const { username } = event.target 22: try { 23: await users(username) 24: setErrors([]) 25: } catch(e) { 26: setErrors(e.errors) 27: } finally { 28: onChange(event) 29: } 30: } 31: 32: return( 33: <form onSubmit={handleSubmit}> 34: <ul> 35: {errors && errors.map((error, index) => <li key={index}>{error}</li>)} 36: </ul> 37: <input type="text" name="username" 38: data-testid="username" placeholder="Usuario" onChange={handleUsernameChange} /> 39: <input type="text" name="password" 40: data-testid="password" placeholder="Password" onChange={onChange} /> 41: <input type="text" name="birthdate" 42: data-testid="birthdate" placeholder="Fecha de nacimiento" onChange={onChange} /> 43: <input type="text" name="email" 44: data-testid="email" placeholder="Correo electrónico" onChange={onChange} /> 45: <button type="submit" disabled={errors.length !== 0}>Guardar</button> 46: </form> 47: )
En la última prueba, hemos usado el endpoint users para validar si un usuario existe, sin embargo, en la prueba “Crear cuenta correctamente”, no.
Veamos la solución y analicemos.
… 5: it('crear cuenta correctamente', async () => { 6: const props = { 7: createAccount: jest.fn(), 8: users: jest.fn().mockResolvedValue(), 9: } 10: 11: render( 12: <CreateTwitchAccount {...props} /> 13: ) 14: 15: await act(() => { 16: fireEvent.change(screen.getByTestId('username'), { target: { value: 'Gabriel' } }) 17: }) …
Línea 8. Agregamos un prop que devuelva una promesa exitosa. Esta promesa simula que un usuario no existe.
Línea 15..17. Tenemos que esperar hasta que la promesa se resuelva o en términos menos técnicos que el endpoint nos devuelva su respuesta.