testing-library react:测试由上下文函数和变量触发(useEffect)的变化

testing-library react: test changes that is triggered(useEffect) by context functions and variables

在使用测试 React 库时,我到处搜索最佳实践。我有一个使用反应上下文的测试,我试图用一个组件测试它以确保它更新正确。这是相当多的代码,所以我会在这里缩小范围(尽管仍然很多)。

可在此处找到此代码的工作版本https://codesandbox.io/s/react-playground-forked-mllwv8(尽管尚未设置测试平台,因此这可能无济于事,因为不确定如何在 cs 上实现 webpack)

运行 的功能是:

我要测试:

考虑到我的设置,最好的方法是什么? 请记住,这段代码实际上与我正在使用的代码非常不同,但机制的使用方式相同

fetchPeople.jsx

export const fetchPeople = (dispatch) => {
  // faking a request here for the sake of demonstration
  const people = [
    {
      name: "Tommy",
      age: 24
    }
  ];
  setTimeout(() => {
    dispatch({
      type: "SET_PEOPLE",
      payload: people
    });
  }, 5000);
};

PeoplePageComponent.jsx

import React, { useContext, useEffect } from "react";
import { MyContext } from "./MyProvider";

export const PeoplePageComponent = () => {
  const { currentPage, people, setPage, fetchPeople } = useContext(MyContext);

  useEffect(() => {
    if (!people) return;
    setPage(people.length > 0 ? "hasPeople" : "noPeople");
  }, [people]);

  useEffect(() => {
    fetchPeople();
  }, []);

  return (
    <>
      <p>
        <b>Page:</b> {currentPage}
      </p>

      <p>
        <b>People:</b> {JSON.stringify(people)}
      </p>
      {currentPage === "loading" && <h1>This is the loading page</h1>}
      {currentPage === "noPeople" && <h1>This is the noPeople page</h1>}
      {currentPage === "hasPeople" && <h1>This is the hasPeople page</h1>}
    </>
  );
};

MyProvider.jsx

import { createContext, useReducer } from "react";
import { fetchPeople } from "./fetchPeople";

const reducer = (state, action) => {
  switch (action.type) {
    case "SET_PAGE":
      return {
        ...state,
        currentPage: action.payload
      };
    case "SET_PEOPLE":
      return {
        ...state,
        people: action.payload
      };
    default:
      return state;
  }
};

const initialState = {
  currentPage: "loading",
  people: null
};

export const MyContext = createContext({
  setPage: () => {},
  setPeople: () => {},
  fetchPeople: () => {},
  currentPage: "loading",
  people: null
});

export const MyProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);

  const providerValue = {
    setPage: (page) => dispatch({ type: "SET_PAGE", payload: page }),
    setPeople: (people) => dispatch({ type: "SET_PEOPLE", payload: people }),
    fetchPeople: () => fetchPeople(dispatch),
    currentPage: state.currentPage,
    people: state.people
  };
  return (
    <MyContext.Provider value={providerValue}>{children}</MyContext.Provider>
  );
};

index.js

import React from "react";
import ReactDOM from "react-dom";

import { MyProvider } from "../src/MyProvider";

import { PeoplePageComponent } from "../src/PeoplePageComponent";

const App = () => {
  return (
    <MyProvider>
      <main>
        <PeoplePageComponent />
      </main>
    </MyProvider>
  );
};

ReactDOM.render(<App />, document.getElementById("container"));

PeoplePageComponent.test.jsx

import { render } from "@testing-library/react";
import { PagePeopleComponent } from "../PeoplePageComponent";
import { MyContext } from "../MyProvider";

const mockedContextValue = {
  setPage: jest.fn(),
  setPeople: jest.fn(),
  currentPage: "loading",
  people: null
};

test("sets page correctly", () => {
  const contextValue = {
    ...mockedContextValue
  };
  const { rerender, container } = render(
    <MyContext.Provider value={contextValue}>
      <PagePeopleComponent />
    </MyContext.Provider>
  );

  expect(container).toHaveTextContent(/This is the loading page/i);

  rerender(
    <MyContext.Provider value={{ ...contextValue, nominees: [] }}>
      <PagePeopleComponent />
    </MyContext.Provider>
  );

  expect(container).toHaveTextContent(/This is the hasPeople page/i);
});


在不更改应用程序代码的情况下,您无法模拟上下文值,但可以模拟 fetchPeople 函数。我假设在真实代码中这是一个网络请求,因此无论如何都应该对其进行模拟。此外,不模拟上下文提供者会使测试更加健壮,因为它 resemble more the way the software is used。事实上,它不模拟上下文更新,而是测试页面如何操作上下文。

import { render, screen } from "@testing-library/react";
import { PeoplePageComponent } from "../PeoplePageComponent";
import { MyProvider } from "../MyProvider";
import * as api from "../fetchPeople";

beforeEach(() => {
  jest.spyOn(api, "fetchPeople").mockImplementation(async (dispatch) => {
    setTimeout(() => {
      dispatch({
        type: "SET_PEOPLE",
        payload: [
          { name: "Tommy", age: 24 },
          { name: "John", age: 25 },
        ],
      });
    }, 1);
  });
});

test("sets page correctly", async () => {
  render(
    <MyProvider>
      <PeoplePageComponent />
    </MyProvider>
  );

  const loadingText = screen.getByText(/This is the loading page/i);
  expect(loadingText).toBeInTheDocument();
  expect(api.fetchPeople).toHaveBeenCalledTimes(1);

  const peopleText = await screen.findByText(/This is the hasPeople page/i);
  expect(peopleText).toBeInTheDocument();
});