首页 > 解决方案 > 使用上下文和反应钩子测试组件

问题描述

我有一个简单的仪表板组件,它依赖于 React 上下文来管理身份验证。它包含一个自定义钩子useAuth来提取当前用户以及与身份验证相关的功能:登录、注销等。

这是上下文文件AuthContext.js::

import React, { createContext, useContext, useState, useEffect } from "react";
import { auth } from "../config/firebase";

const AuthContext = createContext();

export function useAuth() {
  return useContext(AuthContext);
}

export function AuthProvider({ children }) {
  const [currentUser, setCurrentUser] = useState();
  const [loading, setLoading] = useState(true);

  function signup(email, password) {
    return auth.createUserWithEmailAndPassword(email, password);
  }

  function login(email, password) {
    return auth.signInWithEmailAndPassword(email, password);
  }

  function logout() {
    return auth.signOut();
  }

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((user) => {
      setCurrentUser(user);
      setLoading(false);
    });

    return unsubscribe;
  }, []);

  const value = {
    currentUser,
    signup,
    login,
    logout,
  };

  return (
    <AuthContext.Provider value={value}>
      {!loading && children}
    </AuthContext.Provider>
  );
}

这是Dashboard.js组件:

import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { useAuth } from "../context/AuthContext";

export default function Dashboard() {
  const { currentUser, logout } = useAuth();
  const [error, setError] = useState("");
  const history = useHistory();

  const handleLogout = async () => {
    setError("");
    try {
      await logout();
      history.push("/login");
    } catch (e) {
      setError(e.message);
    }
  };
  return (
    <div>
      {error && <p>{error}</p>}
      <h1>This is the Dashboard</h1>
      <h5>Email: {currentUser.email}</h5>
      <button onClick={handleLogout} type="button">
        Logout
      </button>
    </div>
  );
}

根据React 测试库的推荐,我创建了一个 test-utils.js 文件:

import React, { createContext } from "react";
import { render } from "@testing-library/react";
import { BrowserRouter as Router } from "react-router-dom";

const AuthContext = createContext();

const currentUser = {
  email: "abc@abc.com",
};

const signup = jest.fn();
const login = jest.fn();
const logout = jest.fn();

const AllTheProviders = ({ children }) => {
  return (
    <Router>
      <AuthContext.Provider value={{ currentUser, signup, login, logout }}>
        {children}
      </AuthContext.Provider>
    </Router>
  );
};

const customRender = (ui, options) => {
  render(ui, { wrapper: AllTheProviders, ...options });
};

export * from "@testing-library/react";

export { customRender as render };

但是,运行时Dashboard.test.js出现错误

    TypeError: Cannot destructure property 'currentUser' of '((cov_5mwatn2cf(...).s[0]++) , (0 , _AuthContext.useAuth)(...))' as it is undefined.

      4 | 
      5 | export default function Dashboard() {
    > 6 |   const { currentUser, logout } = useAuth();
        |           ^
      7 |   const [error, setError] = useState("");
      8 |   const history = useHistory();

import React from "react";
import Dashboard from "./Dashboard";
import { act, render, screen } from "../config/test-utils-dva";

beforeEach(async () => {
  await act(async () => {
    render(<Dashboard />);
  });
});

test("displays dashboard", () => {
  expect(screen.getByText(/dashboard/i)).toBeInTheDocument();
});

我认为这是因为 Dashboard 组件正在尝试使用useAuthfrom AuthContext.js,如何强制渲染的 Dashboard 组件使用我在test-utils.js文件中发送的模拟数据?

标签: reactjsreact-testing-library

解决方案


不要创建新的上下文,而是使用AuthContextfrom context/AuthContextfor <AuthContext.Provider>,因为这是钩子使用的上下文。

因此,在 中AuthContext.js,导出上下文实例:

export const AuthContext = createContext();

然后,在您的test-util.js文件中,而不是再次调用createContext(这将创建一个完全独立的上下文实例 - 即使它们存储在具有相同名称的变量中,上下文也不相同!),只需导入先前导出的实例:

import { AuthContext } from "../context/AuthContext";

const AllTheProviders = ({ children }) => {
  return (
    <Router>
      <AuthContext.Provider value={{ currentUser, signup, login, logout }}>
        {children}
      </AuthContext.Provider>
    </Router>
  );
};

推荐阅读