使用 UseReducer 和 UseContext 反应测试库未正确更新状态
React Testing Library with UseReducer & UseContext not updating state properly
我创建了一个沙盒来概述主要兴趣点:
https://codesandbox.io/s/restless-dawn-nwy0l
请忽略格式,因为这只是我放在一起的 MWE。
当我运行下面test在上面的沙盒
import React from "react";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import Statistics from "../src/components/Block/Statistics";
import { IAction, IState } from "../src/typings/AppTypes";
import { AppReducer } from "../src/reducers/AppReducer";
import { AppContext } from "../src/context/AppContext";
import * as MineUtil from "../src/utils/mine";
import * as ConversionUtil from "../src/utils/conversion";
const state: IState = {
verifiedTrans: [
{
to: "A",
from: "B",
amount: 1.23,
message: "First Transaction",
signature: "AB1.23"
},
{
to: "A",
from: "C",
amount: 456.78,
message: "Second Transaction",
signature: "AC456.78"
},
{
to: "A",
from: "D",
amount: 999.99,
message: "Third Transaction",
signature: "AD999.99"
},
{
to: "A",
from: "E",
amount: 987.65,
message: "Forth Transaction",
signature: "AE987.65"
},
{
to: "A",
from: "F",
amount: 1.01,
message: "Fifth Transaction",
signature: "AF1.01"
}
],
selectedTrans: [
{
to: "A",
from: "C",
amount: 456.78,
message: "Second Transaction",
signature: "AC456.78"
}
],
chain: [
{
index: 0,
prevHash: "",
currHash: new Array(64).fill("0").join(""),
transactions: [],
timestamp: Date.parse("04/31/2021"),
merkleRoot: "",
valid: true
},
{
index: 1,
prevHash: new Array(64).fill("0").join(""),
currHash: new Array(64).fill("A").join(""),
transactions: [
{
to: "A",
from: "E",
amount: 987.65,
message: "Forth Transaction",
signature: "AE987.65"
}
],
timestamp: Date.parse("05/01/2021"),
merkleRoot: "987.65EForthTransactionAE987.65A",
valid: true
}
],
preview: {
index: 2,
timestamp: Date.parse("05/02/2021"),
prevHash: new Array(64).fill("A").join(""),
currHash: "",
transactions: [],
merkleRoot: "",
valid: false
}
};
const dispatch = (action: IAction) => AppReducer(state, action);
it("keeps mining button disabled after mining due to valid solution", async () => {
const solution =
"000a4fda363405b2796986a63e8cedde080e1f29ed774f5f93bd97c42b9a96fc0";
const target =
"000b4fda363405b2796986a63e8cedde080e1f29ed774f5f93bd97c42b9a96fc0";
jest.spyOn(MineUtil, "createTarget").mockReturnValue(Promise.resolve(target));
jest
.spyOn(ConversionUtil, "digestMessage")
.mockReturnValue(Promise.resolve(solution));
render(
<AppContext.Provider value={{ state, dispatch }}>
<Statistics chain={false} />
</AppContext.Provider>
);
expect(screen.getByRole("button", { name: /Block Mine/i })).toBeEnabled();
// need to await state changes
fireEvent.click(screen.getByRole("button", { name: /Block Mine/i }));
await waitFor(() => {
expect(screen.getByRole("button", { name: /Block Mine/i })).toBeDisabled();
});
await waitFor(() => {
expect(
screen.getByRole("textbox", { name: /Block Solution/i })
).toHaveClass("valid-solution");
});
});
我得到:
该测试只是检查,如果您在选择交易后按下挖矿按钮,该按钮将被禁用,一旦 有效 解决方案被挖出,它就会 class更改,因此它变为绿色文本(对于此组件)。由于解决方案有效,该按钮也应保持禁用状态。
此外,预览块将变为绿色,但这超出了本单元测试的范围。
但是,状态预览块(确定解决方案输入文本框上的 class)似乎在测试环境中没有正确更新。在开发过程中,情况并非如此,一切都按预期进行。
任何suggestions/hints?
在您的测试中,您设置了 value, dispatch
的模拟版本,当您单击 Block mine
按钮时它不会触发更新。
但在实际代码中,在 App.tsx
中你使用了 useReducer
钩子(在深处它触发重新渲染并且新的道具通过 Context 传递给 Statistics
)。
要修复它,只需使用 useReducer
模拟您的测试
const Wrapper = () => {
const [state, dispatch] = useReducer(AppReducer, inititalValues);
return (
<AppContext.Provider value={{ state, dispatch }}>
<Statistics chain={false} />
</AppContext.Provider>
);
};
render(<Wrapper />);
inititalValues
是第12行的变量state
工作沙箱here
输出:
我创建了一个沙盒来概述主要兴趣点: https://codesandbox.io/s/restless-dawn-nwy0l
请忽略格式,因为这只是我放在一起的 MWE。
当我运行下面test在上面的沙盒
import React from "react";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import Statistics from "../src/components/Block/Statistics";
import { IAction, IState } from "../src/typings/AppTypes";
import { AppReducer } from "../src/reducers/AppReducer";
import { AppContext } from "../src/context/AppContext";
import * as MineUtil from "../src/utils/mine";
import * as ConversionUtil from "../src/utils/conversion";
const state: IState = {
verifiedTrans: [
{
to: "A",
from: "B",
amount: 1.23,
message: "First Transaction",
signature: "AB1.23"
},
{
to: "A",
from: "C",
amount: 456.78,
message: "Second Transaction",
signature: "AC456.78"
},
{
to: "A",
from: "D",
amount: 999.99,
message: "Third Transaction",
signature: "AD999.99"
},
{
to: "A",
from: "E",
amount: 987.65,
message: "Forth Transaction",
signature: "AE987.65"
},
{
to: "A",
from: "F",
amount: 1.01,
message: "Fifth Transaction",
signature: "AF1.01"
}
],
selectedTrans: [
{
to: "A",
from: "C",
amount: 456.78,
message: "Second Transaction",
signature: "AC456.78"
}
],
chain: [
{
index: 0,
prevHash: "",
currHash: new Array(64).fill("0").join(""),
transactions: [],
timestamp: Date.parse("04/31/2021"),
merkleRoot: "",
valid: true
},
{
index: 1,
prevHash: new Array(64).fill("0").join(""),
currHash: new Array(64).fill("A").join(""),
transactions: [
{
to: "A",
from: "E",
amount: 987.65,
message: "Forth Transaction",
signature: "AE987.65"
}
],
timestamp: Date.parse("05/01/2021"),
merkleRoot: "987.65EForthTransactionAE987.65A",
valid: true
}
],
preview: {
index: 2,
timestamp: Date.parse("05/02/2021"),
prevHash: new Array(64).fill("A").join(""),
currHash: "",
transactions: [],
merkleRoot: "",
valid: false
}
};
const dispatch = (action: IAction) => AppReducer(state, action);
it("keeps mining button disabled after mining due to valid solution", async () => {
const solution =
"000a4fda363405b2796986a63e8cedde080e1f29ed774f5f93bd97c42b9a96fc0";
const target =
"000b4fda363405b2796986a63e8cedde080e1f29ed774f5f93bd97c42b9a96fc0";
jest.spyOn(MineUtil, "createTarget").mockReturnValue(Promise.resolve(target));
jest
.spyOn(ConversionUtil, "digestMessage")
.mockReturnValue(Promise.resolve(solution));
render(
<AppContext.Provider value={{ state, dispatch }}>
<Statistics chain={false} />
</AppContext.Provider>
);
expect(screen.getByRole("button", { name: /Block Mine/i })).toBeEnabled();
// need to await state changes
fireEvent.click(screen.getByRole("button", { name: /Block Mine/i }));
await waitFor(() => {
expect(screen.getByRole("button", { name: /Block Mine/i })).toBeDisabled();
});
await waitFor(() => {
expect(
screen.getByRole("textbox", { name: /Block Solution/i })
).toHaveClass("valid-solution");
});
});
我得到:
该测试只是检查,如果您在选择交易后按下挖矿按钮,该按钮将被禁用,一旦 有效 解决方案被挖出,它就会 class更改,因此它变为绿色文本(对于此组件)。由于解决方案有效,该按钮也应保持禁用状态。
此外,预览块将变为绿色,但这超出了本单元测试的范围。
但是,状态预览块(确定解决方案输入文本框上的 class)似乎在测试环境中没有正确更新。在开发过程中,情况并非如此,一切都按预期进行。
任何suggestions/hints?
在您的测试中,您设置了 value, dispatch
的模拟版本,当您单击 Block mine
按钮时它不会触发更新。
但在实际代码中,在 App.tsx
中你使用了 useReducer
钩子(在深处它触发重新渲染并且新的道具通过 Context 传递给 Statistics
)。
要修复它,只需使用 useReducer
const Wrapper = () => {
const [state, dispatch] = useReducer(AppReducer, inititalValues);
return (
<AppContext.Provider value={{ state, dispatch }}>
<Statistics chain={false} />
</AppContext.Provider>
);
};
render(<Wrapper />);
inititalValues
是第12行的变量state
工作沙箱here
输出: