英文:
Unit test for React App: useNavigate problem
问题
I'm sorry, but I can't execute or test code directly. However, I can help you analyze the issue in your test code.
In your last test, "should navigate to dashboard when user and isPending are false," you mentioned that you expect the "/dashboard" path to be called. To ensure this test works as expected, you should check a few things:
- Make sure that the
useUserContxt
mock is correctly returning a user object withisPending
set tofalse
:
useUserContxt.mockReturnValue({
user: {},
isPending: false,
dispatch: mockDispatch,
});
- Ensure that the
mockedUsedNavigate
function is being called correctly. Verify that it's properly imported and used within yourLogin
component. Make sure that it's actually called when the conditions in theuseEffect
of yourLogin
component are met.
If you've already checked these points and the test is still failing, you may need to provide more specific information about the error or issue you're encountering for further assistance.
英文:
I'm writing a test unit for my Login component, but I'm stuck in a useNavigate problem, I couldn't find a solution for it, I'm using react 18, so I couldn't use testing-library/react-hooks or enzyme.
here is my Login.jsx:
import React, { useEffect, useState } from "react";
import {
Button,
FormControl,
Input,
InputAdornment,
InputLabel,
Box,
Alert,
} from "@mui/material";
import AccountCircle from "@mui/icons-material/AccountCircle";
import HttpsIcon from "@mui/icons-material/Https";
import logo from "../assets/logo_black_back.png";
import { ReactComponent as Svg } from "../assets/animation.svg";
import { PendingPage } from "../components";
import "./login.css";
import { Link, useNavigate } from "react-router-dom";
import { useLogin } from "../hooks/useLogin";
import { useUserContxt } from "../context/authenticationContext";
function Login() {
const [email, setEmail] = useState();
const [password, setPassword] = useState();
const { login, error } = useLogin();
const { user, isPending } = useUserContxt();
const navigate = useNavigate();
const submit = (e) => {
e.preventDefault();
login(email, password);
};
useEffect(() => {
if (user && isPending === false) {
navigate("/dashboard");
}
}, [user, isPending, navigate]);
return !user && isPending === false ? (
<Box
sx={{
width: "100%",
height: "100vh",
display: "flex",
justifyContent: "center",
}}
>
<Box
sx={{
flex: 1,
display: { xs: "none", lg: "flex" },
alignItems: "center",
justifyContent: "center",
}}
>
<Box>
<Svg />
</Box>
</Box>
<Box
sx={{
flex: 1,
display: "flex",
boxShadow: 1,
borderRadius: "40px 0 0 40px",
flexDirection: "column",
p: 2,
justifyContent: "flex-start",
alignItems: "center",
}}
>
<Box sx={{ width: 150, height: 150, m: 10, mb: 5 }}>
<img src={logo} alt="logo" />
</Box>
<Box
onSubmit={submit}
component="form"
sx={{ width: { xs: "350px", lg: "400px" } }}
>
<FormControl variant="standard" sx={{ width: "100%", mb: 5 }}>
<InputLabel htmlFor="email">EMAIL</InputLabel>
<Input
onChange={(e) => setEmail(e.target.value)}
id="email"
startAdornment={
<InputAdornment position="start">
<AccountCircle />
</InputAdornment>
}
/>
</FormControl>
<FormControl variant="standard" sx={{ width: "100%", mb: 5 }}>
<InputLabel htmlFor="password">PASSWORD</InputLabel>
<Input
onChange={(e) => setPassword(e.target.value)}
id="password"
type="password"
startAdornment={
<InputAdornment position="start">
<HttpsIcon />
</InputAdornment>
}
/>
</FormControl>
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
<Button type="submit" variant="contained">
LOGIN
</Button>
<Link to="/" style={{ textDecoration: "none" }}>
<Button variant="text" color="error">
BACK TO HOME
</Button>
</Link>
</Box>
{error && <Alert severity="warning">{error}</Alert>}
</Box>
</Box>
</Box>
) : (
<PendingPage />
);
}
export default Login;
and here is the test code:
import React from 'react';
import '@testing-library/jest-dom';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { useLogin } from '../hooks/useLogin';
import { useUserContxt } from '../context/authenticationContext';
import Login from './Login';
jest.mock('../hooks/useLogin');
jest.mock('../context/authenticationContext');
describe('Login', () => {
const mockLogin = jest.fn();
const mockDispatch = jest.fn();
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate
}));
beforeEach(() => {
useLogin.mockReturnValue({
login: mockLogin,
error: null,
});
useUserContxt.mockReturnValue({
user: null,
isPending: false,
dispatch: mockDispatch,
});
jest.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
jest.clearAllMocks();
});
afterAll(() => {
jest.restoreAllMocks();
});
it('should render Login component correctly', () => {
render(
<BrowserRouter>
<Login />
</BrowserRouter>
);
expect(screen.getByText('EMAIL')).toBeInTheDocument();
expect(screen.getByText('PASSWORD')).toBeInTheDocument();
expect(screen.getByText('LOGIN')).toBeInTheDocument();
expect(screen.getByText('BACK TO HOME')).toBeInTheDocument();
});
it('should update email and password state when typing', async () => {
render(
<BrowserRouter>
<Login />
</BrowserRouter>
);
const emailInput = screen.getByLabelText('EMAIL');
const passwordInput = screen.getByLabelText('PASSWORD');
fireEvent.change(emailInput, { target: { value: 'test@test.com' } });
fireEvent.change(passwordInput, { target: { value: 'password123' } });
await waitFor(() => {
expect(emailInput).toHaveValue('test@test.com');
expect(passwordInput).toHaveValue('password123');
});
});
it('should call login function when form is submitted', async () => {
render(
<BrowserRouter>
<Login />
</BrowserRouter>
);
const emailInput = screen.getByLabelText('EMAIL');
const passwordInput = screen.getByLabelText('PASSWORD');
const loginButton = screen.getByText('LOGIN');
fireEvent.change(emailInput, { target: { value: 'test@test.com' } });
fireEvent.change(passwordInput, { target: { value: 'password123' } });
fireEvent.click(loginButton);
await waitFor(() => {
expect(mockLogin).toHaveBeenCalledWith('test@test.com', 'password123');
});
});
it('should navigate to dashboard when user and isPending are false', async () => {
useUserContxt.mockReturnValue({
user: {},
isPending: false,
dispatch: mockDispatch,
});
render(
<BrowserRouter>
<Login />
</BrowserRouter>
);
await waitFor(() => {
expect(mockedUsedNavigate).toHaveBeenCalledWith('/dashboard');
});
});
});
the problem is in the last test "should navigate to dashboard when user and isPending are false"
I'm expecting the "/dashboard" path to be called.
答案1
得分: 2
I had a similar issue. I fixed it by moving the mock code to the global scope at the start of the file (prior to the test code).
Specifically, for you, it would look like
import Login from './Login';
jest.mock('../hooks/useLogin');
jest.mock('../context/authenticationContext');
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate
}));
describe('Login', () => {
const mockLogin = jest.fn();
const mockDispatch = jest.fn();
beforeEach(() => {
[...snip...]
英文:
I had a similar issue. I fixed it by moving the mock code to the global scope at the start of the file (prior to the test code).
Specifically, for you, it would look like
import Login from './Login';
jest.mock('../hooks/useLogin');
jest.mock('../context/authenticationContext');
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate
}));
describe('Login', () => {
const mockLogin = jest.fn();
const mockDispatch = jest.fn();
beforeEach(() => {
[...snip...]
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论