Publicación: 6 months

En el post anterior, 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.

  1. Usuario ingresa nombre de usuario
  2. Se valida con el backend la existencia del usuario
  3. En caso de existir, mostramos mensaje de error y deshabilitamos botón de guardar

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

Ejecutemos la prueba.

Error, el texto "Usuario Gabriel ya existe" no se cuentra.


Como era de esperarse nos ha arrojado un error 😣 no ha encontrado el mensaje “El usuario Gabriel ya existe”.

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 usuario
Línea 47. Deshabilitamos el botón si existen errores

Ejecutemos nuestra prueba y crucemos dedos 🤞🏼

Prueba exitosa "Si usuario existe"


Lo hemos logrado 🎉🎉

Para finalizar y como una buena práctica, ejecutemos todas las pruebas.

Ejecución de todas las pruebas con errores


Noooooo 😭, descuida, como lo comentamos arriba, los errores igual son vistos como oportunidades para aprender nuevas cosas. Analicemos, parece que en la línea 45 del componente CreateTwitchAccount esta el problema.

   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.

Ahora si, ejecutemos todas las pruebas.

Ejecución de todas las pruebas exitosamente


¡Por fin!, dame esos cinco ✋