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)
运行 的功能是:
- PeoplePageComponent 加载
- useEffect 运行s 和 运行s 从 CONTEXT
中获取人物
- fetchPeople(上下文函数)运行s fetchPeople(实际函数)来自 fetchPeople.jsx 调度 SET_PEOPLE
- 在PeoplePageComponent中useEffect被触发到逻辑setCurrentPage
我要测试:
- 使用 providerValue (people: null) 渲染 PeoplePageComponent
- 断言页面是
loading
- 断言
mockFetchPeople
被调用了 1 次
- 使用 providerValue 重新渲染 PeoplePageComponent (people:[{name: "tommy", age: 24}])(这是我不确定的部分)
- 断言
mockSetCurrentPage
被调用了 1 次
- 断言页面是
hasPeople
考虑到我的设置,最好的方法是什么?
请记住,这段代码实际上与我正在使用的代码非常不同,但机制的使用方式相同
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();
});
在使用测试 React 库时,我到处搜索最佳实践。我有一个使用反应上下文的测试,我试图用一个组件测试它以确保它更新正确。这是相当多的代码,所以我会在这里缩小范围(尽管仍然很多)。
可在此处找到此代码的工作版本https://codesandbox.io/s/react-playground-forked-mllwv8(尽管尚未设置测试平台,因此这可能无济于事,因为不确定如何在 cs 上实现 webpack)
运行 的功能是:
- PeoplePageComponent 加载
- useEffect 运行s 和 运行s 从 CONTEXT 中获取人物
- fetchPeople(上下文函数)运行s fetchPeople(实际函数)来自 fetchPeople.jsx 调度 SET_PEOPLE
- 在PeoplePageComponent中useEffect被触发到逻辑setCurrentPage
我要测试:
- 使用 providerValue (people: null) 渲染 PeoplePageComponent
- 断言页面是
loading
- 断言
mockFetchPeople
被调用了 1 次 - 使用 providerValue 重新渲染 PeoplePageComponent (people:[{name: "tommy", age: 24}])(这是我不确定的部分)
- 断言
mockSetCurrentPage
被调用了 1 次 - 断言页面是
hasPeople
考虑到我的设置,最好的方法是什么? 请记住,这段代码实际上与我正在使用的代码非常不同,但机制的使用方式相同
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();
});