2023-04-17

Testing de un formulario en React

En este post, se explica cómo utilizar screen, render y expect para realizar pruebas a un formulario en React.

Render, Screen, Expect, Mocks

  • Render:
    Como su nombre indica, lo que va a hacer esto es renderizar el componente que nosotros le pidamos y nos va a retornar el contenedor del DOM donde se está ejecutando la prueba. Para simplificar la explicación, imagínate un vacío (navegador) y dentro de este aparece el componente que renderizaste y eso es lo unico que hay. A partir de ahí, vamos a poder empezar a trabajar.

  • Screen:
    screen es una utilidad que nos permite actuar sobre el contenedor renderizado. Proporciona métodos para buscar elementos en el DOM, como getByRole, getByTestId, etc. Esto facilita la interacción y la búsqueda de elementos específicos para las pruebas.

  • Expect:
    La función expect se utiliza para hacer afirmaciones sobre los elementos del DOM. En la primera parte de la afirmación, se indica qué elemento se va a observar, y en la segunda parte se especifica qué se espera que suceda. Por ejemplo, expect(elemento).toBeInTheDocument() verifica si el elemento está presente en el documento.

  • Mocks:
    Los mocks son datos controlados que se proporcionan a los componentes para obtener respuestas predecibles durante las pruebas.

Carpetas y archivos a crear

  • 📂__test__
    • LoginForm.test.tsx
  • __mocks__
    • LoginForm.mock.ts

Ejemplo básico

import { render } from '@testing-library/react'

// describe(nombreComponente, logica)

describe('LoginForm', () => {
	// Vamos a tomar "it" como cada parte individual del test.
	it('should render correctly', () => {
		const container = render(<LoginForm />) // renderiza <LoginForm/>
		expect(container).toBeInTheDocument() // Si container esta en el documento pasa el test
	})
})

Ya sabiendo la base de cómo vamos a realizar los tests, podemos comenzar a complejizarlos.

Creación de mocks

// Esta va a ser la información "buena"
export const LoginFormMock = {
	username: 'username',
	password: 'pass123@',
}

// Esta va a ser la información erronea
export const LoginFormMockError = {
	username: 'usernameeeeeeeeeeeeeee',
	password: 'pass123',
}

Dos inputs en pantalla

import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { LoginFormMock } from '@/__mocks__/LoginForm.mock.ts'

describe('LoginForm', () => {
	// se ejecuta despues del test y resetea los mocks
	afterEach(cleanup)
	afterEach(jest.clearAllMocks)
	// Este beforeEach es para que se ejecute siempre antes de cualquier test y no tener que repetir constantemente el render
	beforeEach(() => {
		render(<LoginForm />)
	})
	it('should two input and button exists at the screen', () => {
		// Obtenemos los elementos por su rol y name, aunque hay otras formas como el data-testid
		const usernameInput = screen.getByRole('textbox', { name: /Iniciar sesion/i, })
		const passwordInput = screen.getByRole('textbox', { name: /Contraseña/i })
		const buttonSubmit = screen.getByRole('button', { name: /Iniciar sesion/i })

		expect(usernameInput).toBeInTheDocument()
		expect(passwordInput).toBeInTheDocument()
		expect(buttonSubmit).toBeInTheDocument()

		// Aca verifica si el botón está deshabilitado cuando el form no tiene información.
		expect(buttonSubmit).toBeDisabled()
	})

Mismos dos inputs y le sumamos mocks, userEvent

it('should enable the submit button if the form values are valid', async () => {
	const usernameInput = screen.getByRole('textbox', { name: /Iniciar sesion/i })
	const passwordInput = screen.getByRole('textbox', { name: /Contraseña/i })
	const submitButton = screen.getByRole('button', { name: /Iniciar sesion/i })

	expect(submitButton).tobeDisabled()

	// Ahora simulamos que el usuario escribe en el form con la información del mock
	await userEvent.type(usernameInput, LoginFormMock.username)
	await userEvent.type(usernameInput, LoginFormMock.password)

	// El waitFor es una función que espera a que todo lo que está dentro termine; aquí no se utiliza de la mejor manera
	// pero es para el ejemplo
	await waitFor(() => {
		// Vemos si el valor del form conincide con el mock
		expect(usernameInput).toHaveValue(LoginFormMock.username)
		expect(passwordInput).toHaveValue(LoginFormMock.password)
	})
	// y vemos que se habilite el boton
	expect(submitButton).toBeEnabled()
})