React应用的单元测试:useNavigate问题

huangapple go评论65阅读模式
英文:

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:

  1. Make sure that the useUserContxt mock is correctly returning a user object with isPending set to false:
useUserContxt.mockReturnValue({
  user: {},
  isPending: false,
  dispatch: mockDispatch,
});
  1. Ensure that the mockedUsedNavigate function is being called correctly. Verify that it's properly imported and used within your Login component. Make sure that it's actually called when the conditions in the useEffect of your Login 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...]

huangapple
  • 本文由 发表于 2023年5月11日 20:06:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/76227491.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定