Gabriel Jiménez | Hace alrededor de 1 año
En el post anterior: React Testing (Parte 2): Caso práctico con el registro de usuario en Twitch, 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.
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
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
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: });
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