Pruebas de Login Exitoso en Aplicación Web
Clase 9 de 20 • Curso de React Testing Library
Contenido del curso
Clase 9 de 20 • Curso de React Testing Library
Contenido del curso
Mariano Ostric
Wilmer Garzon Cabezas
Luis Fernando Valladares Castro
Juan Jose Bernal Villamarin
Wilmer Garzon Cabezas
Jesús Manuel Alarcón Maldonado
Salome Arboleda
Lizbeth Evelyn Nuñez Rojas
Jair Neri
Carlos Alberto Usuga Martinez
Max Andy Diaz Neyra
Mariana Valencia Gallego
Alejandro Mira
Wilmer Garzon Cabezas
Orlando Manuel Mendoza Vargas
Víctor Rolack
Wilmer Garzon Cabezas
Miguel Giraldo Duque
Cesar González Caballero
Otra forma de que se renderice el componente antes de que se corra cada test podria ser con beforeEach y no ahorramos tener que llamar la funcion en cada it.
describe('<Login />', () => { beforeEach(() => { render( <SessionProvider> <MemoryRouter> <Login /> </MemoryRouter> </SessionProvider> ) }) ```describe('\<Login />', () => { beforeEach(() => { render( \<SessionProvider> \<MemoryRouter> \<Login /> \</MemoryRouter> \</SessionProvider> ) })
¡Excelente observación Mariano!
Efectivamente, usar beforeEach(() => { render(<Login />) }) es una práctica más limpia que nos evita repetir el render en cada test. Esto sigue el principio DRY (Don't Repeat Yourself) y hace nuestros tests más mantenibles.
¡Gracias por compartir esta optimización! 🥇
Excelente aporte!! Incluso podriamos extender esta logica y capturar los elementos HTML que vamos a utilizar en las pruebas dentro del beforeEach. De esta manera, las pruebas unicamente se encargarian de la logica logica correspondiente a la prueba y los datos necesarios ya estarian listos.
describe("<Login />", () => { let usernameInput: HTMLInputElement; let passwordInput: HTMLInputElement; let buttonLogin: HTMLButtonElement; let buttonPasswordVisibility: HTMLButtonElement; beforeEach(() => { render( <SessionProvider> <MemoryRouter> <Login /> </MemoryRouter> </SessionProvider> ); usernameInput = screen.getByPlaceholderText("Username"); passwordInput = screen.getByPlaceholderText("Password"); buttonLogin = screen.getByRole("button", { name: "Login" }); buttonPasswordVisibility = screen.getByRole("button", { name: "show" }); }); // Ahora seguirias las pruebas ->
El reto de probar si es password o text al hacer click en el botón show, seria algo así
it('should show password', async () => { handleLogin(); const passwordInput = screen.getByPlaceholderText("Password"); const buttonTogglePassword = screen.getByRole("button", { name: "show" }); await act(() => { fireEvent.click(buttonTogglePassword); }); expect(passwordInput).toHaveAttribute("type", "text"); await act(() => { fireEvent.click(buttonTogglePassword); }); expect(passwordInput).toHaveAttribute("type", "password"); } ); ```it('should show password', async () => { handleLogin();   const passwordInput = screen.getByPlaceholderText("Password"); const buttonTogglePassword = screen.getByRole("button", { name: "show" });   await act(() => { fireEvent.click(buttonTogglePassword); });   expect(passwordInput).toHaveAttribute("type", "text");   await act(() => { fireEvent.click(buttonTogglePassword); });   expect(passwordInput).toHaveAttribute("type", "password"); } );
Gran aporte Juan! 💚
💻👨💻 ¡Mi solución al reto!:
Lo que hice en el primero fue renderizar el componente, luego tomar el input de password y el botón de show/hide. Simule que escribí una contraseña e hice clic, y finalmente compruebo que el input sea de tipo "password", y que el botón haya cambiado su contenido a "hide", que es la lógica que está usando el componente.
it('Deberia mostrar la contraseña y cambiar el boton a hide', async () => { handleLogin(); const passwordInput = screen.getByPlaceholderText("Password"); const toggleButton = screen.getByRole('button', {name: 'show'}); await act(() => { fireEvent.change(passwordInput, {target: { value: "password" } }) fireEvent.click(toggleButton); }); expect(passwordInput).toHaveAttribute('type', 'text'); expect(toggleButton).toHaveTextContent('hide'); })
Para el segundo caso hice lo mismo, pero esta vez haciendo clic dos veces para comprobar la funcionalidad del hide y ¡listo!
it('Deberia ocultar la contraseña y el boton decir show', async () => { handleLogin(); const passwordInput = screen.getByPlaceholderText("Password"); const toggleButton = screen.getByRole('button', {name: 'show'}); await act(() => { fireEvent.change(passwordInput, {target: { value: "password" } }) fireEvent.click(toggleButton); fireEvent.click(toggleButton); }); expect(passwordInput).toHaveAttribute('type', 'password'); expect(toggleButton).toHaveTextContent('show'); })
Hecho:
it('toggle password visibility', async () => { renderLogin(); const passwordInput = screen.getByPlaceholderText('Password'); const toggleButton = screen.getByRole('button', { name: /show|hide/i }); // Initial value expect(passwordInput).toHaveAttribute('type', 'password'); expect(toggleButton).toHaveTextContent('show'); await act(() => { fireEvent.click(toggleButton); }); await waitFor(() => { expect(passwordInput).toHaveAttribute('type', 'text'); expect(toggleButton).toHaveTextContent('hide'); }); });
Hola, resolviendo el reto en dos casos, el primero al hacer un click, que la contraseña se visible y el segundo cuando se vuelve a clickear para que se oculte, me encontré haciendo un doble click, lo cual sería código redundante, por lo que busque una salida a ello y me encontré con que ahora se debería usar el complemento de la librería llamada user-event, no estoy segura de cuan cierto sea, o sí es necesario instalar esta, pero si tiene la ventaja de que actúa directo y no necesita ser envuelta por el act().
it("Debería cambiar de contraseña en asteriscos a descifrado y boton de show a hide", async () => { handleLogin(); const inputPassword = screen.getByPlaceholderText("Password"); const buttonToggle = screen.getByRole("button", { name: "show" }); await act(() => { fireEvent.click(buttonToggle); }); expect(inputPassword).toHaveAttribute("type", "text"); expect(buttonToggle).toHaveTextContent("hide"); }); it("Debería cambiar contraseña de descifrado a asteriscos y boton de hide a show", async () => { handleLogin(); const inputPassword = screen.getByPlaceholderText("Password"); const buttonToggle = screen.getByRole("button", { name: "show" }); // await act(() => { // fireEvent.click(buttonToggle) // fireEvent.click(buttonToggle) // }) // for (let i = 0; i < 2; i++) { await userEvent.click(buttonToggle); } // expect(inputPassword).toHaveAttribute("type", "password"); expect(buttonToggle).toHaveTextContent("show"); });
me aparecia un log sofre efectos en los providers y lo resolvi envolviendo el render en act. que opinan , no estoy del todo seguro si es lo mejor...
const handleLogin = async () => { await act(() => { return render( <SessionProvider> <MemoryRouter> <Login /> </MemoryRouter> </SessionProvider> ) }) }
Aqui mi aporte de como lo resolvi =)
it('debería cambiar la visibilidad de la contraseña al hacer clic en el botón "show/hide"', () => { handleLogin() const passwordInput = screen.getByPlaceholderText('Password') const toggleButton = screen.getByRole('button', { name: /show/i }) // La 'i' en /show/i hace que la búsqueda no distinga mayúsculas de minúsculas. expect(passwordInput).toHaveAttribute('type', 'password') expect(toggleButton).toBeInTheDocument() // Simular click para mostrar contraseña fireEvent.click(toggleButton) expect(passwordInput).toHaveAttribute('type', 'text') expect(screen.getByRole('button', { name: /hide/i })).toBeInTheDocument() // Simular click para ocultar contraseña fireEvent.click(toggleButton) expect(passwordInput).toHaveAttribute('type', 'password') expect(screen.getByRole('button', { name: /show/i })).toBeInTheDocument() })
Yo implemente de esta forma:
it("Deberia mostrar la contraseña", () => { renderLogin(); const passwordInput = screen.getByPlaceholderText("Password"); const showPasswordButton = screen.getByRole("button", { name: "show" }); fireEvent.click(showPasswordButton); expect(passwordInput).toHaveAttribute("type", "text"); }); ``` it("Deberia mostrar la contraseña", () => { renderLogin(); const passwordInput = screen.getByPlaceholderText("Password"); const showPasswordButton = screen.getByRole("button", { name: "show" }); fireEvent.click(showPasswordButton); expect(passwordInput).toHaveAttribute("type", "text"); });
Estas dos últimas clases estuvieron increíbles. Grandes explicaciones
No se si el codigo de mi test es demasiado simple, pero me funciono 😁. it("deberia de cambiar el texto de show a hide y viceversa", async () => { renderLogin() const button = screen.getByText("show") expect(button).toBeInTheDocument() await act(() => [ fireEvent.click(button) ]) const toggleButton = screen.getByText("hide") expect(toggleButton).toBeInTheDocument() })})
it("deberia de cambiar el texto de show a hide y viceversa", async () => { renderLogin() // Primero valido que si este el boton con el texto show const button = screen.getByText("show") expect(button).toBeInTheDocument() // Luego hago la simulacion del click await act(() => [ fireEvent.click(button) ]) // Valido si el texto tiene el nuevo texto deseasdo "hide" const toggleButton = screen.getByText("hide") expect(toggleButton).toBeInTheDocument() })
Excelente aporte Alejandro! 👏
Validando en el caso de prueba que cambie tanto el type del input password y que cambie el texto del botón de "show" a "hide" y viceversa
Hice el reto, pero me surgió un pregunta, ¿es válido colocar un expect entre medio y no al final como lo venimos haciendo en el curso? Ya que hice un expect que verifique que el tipo de input es password antes de gatillar el evento click para que lo transforme en tipo texto
it("should input type change when you show the password", async () => { handleLogin(); const passwordInput = screen.getByPlaceholderText("Password"); const buttonTogglePassword = screen.getByText("show"); expect(passwordInput.getAttribute("type")).toBe("password"); await act(() => { fireEvent.change(passwordInput, { target: { value: "securePassword" } }); fireEvent.click(buttonTogglePassword); }); expect(passwordInput.getAttribute("type")).toBe("text"); });
expect(passwordInput.getAttribute("type")).toBe("password"); await act(() => { fireEvent.change(passwordInput, { target: { value: "securePassword" } }); fireEvent.click(buttonTogglePassword); }); expect(passwordInput.getAttribute("type")).toBe("text"); });
¡Buena pregunta Victor! 💡
Es válido usar múltiples expect en un test, especialmente cuando verificas estados intermedios. Sin embargo, la mejor práctica sugiere seguir el principio de "Una aserción por test" para mantener los tests atómicos y fáciles de debuggear. En tu caso específico, podrías separarlo en dos tests: uno para el estado inicial y otro para la transformación. 🎯
it("Should show the password when clicking show", async () => { render( <SessionProvider> <MemoryRouter> <Login></Login> </MemoryRouter> </SessionProvider> );
const passwordNameInput = screen.getByPlaceholderText("Password"); const buttonShow = screen.getByRole("button", { name: "show" });
await act(() => { fireEvent.change(passwordNameInput, { target: { value: "validPassword" }, }); fireEvent.click(buttonShow); });
const buttonHide = screen.getByText("hide");
expect(buttonHide).toBeInTheDocument(); expect(passwordNameInput).toHaveValue("validPassword"); expect(passwordNameInput).toHaveAttribute("type", "text"); });
it('Shoild it toogle password visdibility', async () => { handleLogin();
const password = 'testPassword'; const { passwordInput } = inputs(); const toogleButton = screen.getByRole('button', { name: 'show' });
expect(toogleButton).toBeInTheDocument(); expect(passwordInput).toBeInTheDocument(); expect(passwordInput).toHaveAttribute('type', 'password'); expect(toogleButton).toHaveTextContent('show');
await act(() => { fireEvent.change(passwordInput, { target: { value: password } }); });
expect(passwordInput).toHaveValue(password);
await act(() => { fireEvent.click(toogleButton); }); expect(passwordInput).toHaveAttribute('type', 'text'); expect(toogleButton).toHaveTextContent('hide');
const passwordValue = passwordInput.getAttribute('value');
expect(passwordValue).toBe(password);
await act(() => { fireEvent.click(toogleButton); });
expect(passwordInput).toHaveAttribute('type', 'password'); expect(toogleButton).toHaveTextContent('show'); });