Publicación: 6 months

En el post anterior, discutimos tres posibles soluciones para el caso de validación de información con el backend. Elegimos la tercera opción, mockear la petición directamente desde una prueba.

Para este post vamos a hacer un par de pruebas para asegurar que los campos se envíen en el formato adecuado. A que me refiero con esto, veamos el ejemplo del formato de correo electrónico.

Formato correo electrónico


La gran mayoría de la información que el usuario ingresa lleva un formato en especifico como es el caso del correo electrónico que todos conocemos. Este tipo de casos son importantes atacarlos tanto en frontend como backend. 

Hace muchos años cuando empezaba a programar, me preguntaba, si solo era suficiente con validar del lado del frontend y el backend dejarlo libre o viceversa. Hoy en día sabemos que esas validaciones deben de ir en el backend si o si. 

Sin embargo, hacer este tipo de validaciones en el frontend tiene sus beneficios. Supongamos que el formulario de registro de Twitch no tuviera la validación del formato de correo electrónico, hacer esta simple petición le costaría algunos centavos 💰 debido a todos los servicios que pueden estar involucrados en comunicarse para devolver una respuesta al frontend.

Ya teniendo conecto sobre la importancia validar los formatos, comencemos con nuestra primera prueba de este post.

src/CreateTwitchAccount.test.js

…
   53:   it('si el formato del correo electrónico es incorrecto, mostrar mensaje de error ', async () => {
   54:     const props = {
   55:       createAccount: jest.fn(),
   56:       users: jest.fn().mockResolvedValue(),
   57:     }
   58: 
   59:     render(
   60:       <CreateTwitchAccount {...props} />
   61:     )
   62: 
   63:     await act(() => {
   64:       fireEvent.change(screen.getByTestId('username'), { target: { value: 'Gabriel' } })
   65:     })
   66:     fireEvent.change(screen.getByTestId('password'), { target: { value: 'Hola1234' } })
   67:     fireEvent.change(screen.getByTestId('birthdate'), { target: { value: '10-01-1990' } })
   68:     fireEvent.change(screen.getByTestId('email'), { target: { value: 'hola@correosinformatovalido' } })
   69: 
   70:     fireEvent.submit(screen.getByText("Guardar"))
   71: 
   72:     expect(document.body.textContent).toContain("El correo electrónico tiene un formato incorrecto")
   73:   });

Si has seguido los últimos posts, esta prueba parece muy similar a “crear una cuenta correctamente”, aunque, hay dos líneas importantes aquí.

Línea 68. Ingresamos un correo electrónico invalido
Línea 72. Se valida que se muestre un mensaje de error si el correo es inválido

Ahora veamos el componente.

src/CreateTwitchAccount.js

…
   11: 
   12:   function validateValuesPreSubmit() {
   13:     const { email } = values
   14: 
   15:     const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
   16:     if(!emailRegex.test(email)) {
   17:       throw new Error("El correo electrónico tiene un formato incorrecto")
   18:     }
   19:   }
   20: 
   21:   function handleSubmit(event) {
   22:     event.preventDefault()
   23: 
   24:     if(errors.length !== 0) {
   25:       return
   26:     }
   27: 
   28:     try {
   29:       validateValuesPreSubmit()
   30:       createAccount({ ...values })
   31:     } catch (e) {
   32:       setErrors([...errors, e.message])
   33:     }
   34:   }
   35: 
…

Línea 12..19. Función para validar los formatos de los campos antes de enviarse al backend
Línea 29. Se llama a la función de validación de formatos
Línea 32. Si hay algún error en los formatos se actualiza el estado de errores y se muestran al usuario

Ejecutemos nuestra prueba.

Prueba exitosa de prueba formato de correo incorrecto


No sé nos olvide, cómo buena práctica ejecutar todas las pruebas.

Pruebas exitosas


Perfecto, todo va bien. Ahora agreguemos la prueba para validar el formato de una contraseña.

src/CreateTwitchAccount.test.js

…
   75:   it('si el formato de la contraseña es incorrecto, mostrar mensaje de error', async () => {
   76:     const props = {
   77:       createAccount: jest.fn(),
   78:       users: jest.fn().mockResolvedValue(),
   79:     }
   80: 
   81:     render(
   82:       <CreateTwitchAccount {...props} />
   83:     )
   84: 
   85:     await act(() => {
   86:       fireEvent.change(screen.getByTestId('username'), { target: { value: 'Gabriel' } })
   87:     })
   88:     fireEvent.change(screen.getByTestId('password'), { target: { value: 'Hola1234' } })
   89:     fireEvent.change(screen.getByTestId('birthdate'), { target: { value: '10-01-1990' } })
   90:     fireEvent.change(screen.getByTestId('email'), { target: { value: '[email protected]' } })
   91: 
   92:     fireEvent.submit(screen.getByText("Guardar"))
   93: 
   94:     expect(document.body.textContent).toContain("La contraseña tiene un formato incorrecto")
   95:   });

A simple vista la prueba luce muy similar a la anterior, sin embargo, la línea 94 valida que exista un mensaje de contraseña incorrecta. Agreguemos el código en el componente para que esta prueba pase.

src/CreateTwitchAccount.js

…
   12:   function validateValuesPreSubmit() {
   13:     const { email, password } = values
   14: 
   15:     const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
   16:     if(!emailRegex.test(email)) {
   17:       throw new Error("El correo electrónico tiene un formato incorrecto")
   18:     }
   19: 
   20:     const passwordRegex = /^(?!.*([A-Za-z0-9!@#$%^&*()_+=-])\1)(?!.*twitch)(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+=-])[A-Za-z\d!@#$%^&*()_+=-]{8,}$/
   21:     if(!passwordRegex.test(password)) {
   22:       throw new Error("La contraseña tiene un formato incorrecto")
   23:     }
  24:   }
…

Analicemos

Línea 13. Obtenemos el campo password
Línea 20. Creamos una expresión regular para validar la contraseña
Línea 21..23. Si la contraseña no cumple con las siguientes condiciones, mostramos mensaje de error

Condiciones de la contraseña

  • Mínimo 8 caracteres 
  • Una letra mayúscula
  • Un número 
  • Un carácter especial 
  • No puede incluir secuencias de caracteres repetitivas 
  • No puede contener la palabra twitch

Ejecutemos la prueba.

Prueba exitosa de prueba formato de contraseña


Perfecto, lo hemos logrado. Ahora como buena práctica, ejecutemos todas.

Error al ejecutar todas las pruebas


Nooo 🙁 , analicemos que está sucediendo. El error dice que no se ha invocado la función “createAccount” y esta en lo correcto, porque la contraseña “'Hola1234“ tiene un formato incorrecto, lo que hace que entre al catch.

src/CreateTwitchAccount.js

…
   26:   function handleSubmit(event) {
   27:     event.preventDefault()
   28: 
   29:     if(errors.length !== 0) {
   30:       return
   31:     }
   32: 
   33:     try {
   34:       validateValuesPreSubmit() // Arroja error de contraseña con formato invalido
   35:       createAccount({ ...values })
   36:     } catch (e) {
   37:       setErrors([...errors, e.message]) // Aquí estamos
   38:     }
   39:   }
…

Hagamos el cambio de contraseña por una valida.

    5:   it('crear cuenta correctamente', async () => {
…
   18:     fireEvent.change(screen.getByTestId('password'), { target: { value: 'Hola1234#' } })
…
   23: 
   24:     expect(props.createAccount).toHaveBeenCalledWith({
   25:       username: "Gabriel",
   26:       password: "Hola1234#",
   27:       email: "[email protected]",
   28:       birthdate: "10-01-1990"
   29:     })
   30:   });

De nuevo, ejecutemos todas las pruebas.

Ejecución de todas las pruebas después de corregir error


🎉🎉 Felicidades, lo hemos logrado.

Antes de saltar al siguiente post, vamos a refactorizar el código de nuestras pruebas porque esta creciendo demasiado y hay mucho código repetitivo.

src/CreateTwitchAccount.test.js

    1: import {render} from "@testing-library/react";
    2: import CreateTwitchAccount from "./CreateTwitchAccount";
    3: import CreateTwitchAccountPageObject from "./CreateTwitchAccountPageObject";
    4: 
    5: describe('CreateTwitchAccount', () => {
    6:   let props
    7:   let wrapper
    8:   beforeEach(() => {
    9:     props = {
   10:       createAccount: jest.fn(),
   11:       users: jest.fn().mockResolvedValue(),
   12:     }
   13: 
   14:     wrapper = () =>
   15:       render(
   16:         <CreateTwitchAccount {...props} />
   17:       )
   18:   })
   19: 
   20:   it('crear cuenta correctamente', async () => {
   21:     wrapper()
   22: 
   23:     await CreateTwitchAccountPageObject.fillUpUsername('Gabriel')
   24:     CreateTwitchAccountPageObject.fillUpPassword('Hola1234#')
   25:     CreateTwitchAccountPageObject.fillUpBirthdate('10-01-1990')
   26:     CreateTwitchAccountPageObject.fillUpEmail('[email protected]')
   27: 
   28:     CreateTwitchAccountPageObject.submit()
   29: 
   30:     expect(props.createAccount).toHaveBeenCalledWith({
   31:       username: "Gabriel",
   32:       password: "Hola1234#",
   33:       email: "[email protected]",
   34:       birthdate: "10-01-1990"
   35:     })
   36:   });
   37: 
   38:   it('si el usuario se repite mostrar mensaje de error', async () => {
   39:     props.users.mockRejectedValue({
   40:       errors: ["El usuario Geovanni ya existe"]
   41:     })
   42: 
   43:     wrapper()
   44: 
   45:     await CreateTwitchAccountPageObject.fillUpUsername('Geovanni')
   46: 
   47:     expect(document.body.textContent).toContain("El usuario Geovanni ya existe")
   48:   });
   49: 
   50:   it('si el formato del correo electrónico es incorrecto, mostrar mensaje de error ', async () => {
   51:     wrapper()
   52: 
   53:     await CreateTwitchAccountPageObject.fillUpForm()
   54:     CreateTwitchAccountPageObject.fillUpEmail('hola@correosinformatovalido')
   55: 
   56:     CreateTwitchAccountPageObject.submit()
   57: 
   58:     expect(document.body.textContent).toContain("El correo electrónico tiene un formato incorrecto")
   59:   });
   60: 
   61:   it('si el formato de la contraseña es incorrecto, mostrar mensaje de error', async () => {
   62:     wrapper()
   63: 
   64:     await CreateTwitchAccountPageObject.fillUpForm()
   65:     CreateTwitchAccountPageObject.fillUpPassword('hola1234')
   66: 
   67:     CreateTwitchAccountPageObject.submit()
   68: 
   69:     expect(document.body.textContent).toContain("La contraseña tiene un formato incorrecto")
   70:   });
   71: });

En las pruebas automatizadas hay un patrón conocido como Page Object. Este patrón nos permite encapsular toda la lógica de un componente o página web para poder acceder a su interfaz de una forma más limpia y amigable.

src/CreateTwitchAccountPageObject.js

    1: import {act, fireEvent, screen} from "@testing-library/react";
    2: 
    3: class CreateTwitchAccountPageObject {
    4:   static submit() {
    5:     fireEvent.submit(screen.getByText("Guardar"))
    6:   }
    7: 
    8:   static async fillUpUsername(value) {
    9:     await act(() => {
   10:       fireEvent.change(screen.getByTestId('username'), { target: { value } })
   11:     })
   12:   }
   13: 
   14:   static fillUpPassword(value) {
   15:     fireEvent.change(screen.getByTestId('password'), { target: { value } })
   16:   }
   17: 
   18:   static fillUpBirthdate(value) {
   19:     fireEvent.change(screen.getByTestId('birthdate'), { target: { value } })
   20:   }
   21: 
   22:   static fillUpEmail(value) {
   23:     fireEvent.change(screen.getByTestId('email'), { target: { value } })
   24:   }
   25: 
   26:   static async fillUpForm() {
   27:     await this.fillUpUsername('Gabriel')
   28:     this.fillUpPassword(‘Hola1234#')
   29:     this.fillUpBirthdate('10-01-1990')
   30:     this.fillUpEmail('[email protected]')
   31:   }
   32: }
   33: 
   34: export default CreateTwitchAccountPageObject

Ya que hemos hecho este refactor, volvamos a ejecutar todas nuestras pruebas como una buena práctica.

Ejecución de todas las pruebas después de refactorización


Como se pueden dar cuenta, el tener pruebas nos permite hacer refactorización tanto del código de pruebas cómo el código de producción sin miedo a romper algo 😎.