我如何访问这个 Promise 调用中的历史对象?
Posted
技术标签:
【中文标题】我如何访问这个 Promise 调用中的历史对象?【英文标题】:How can i access the history object inside of this promise call? 【发布时间】:2021-05-13 17:47:30 【问题描述】:在 React 组件中,按钮提交获取页面上文本字段的值并将它们传递给 mockFetch。然后,如果 mockFetch 的 promise 成功,它会触发 history.push('/newaccount')
。
我将测试设置为单击按钮,然后尝试使用我传递给它的内容来检测history.push('/newaccount')
调用,但我的模拟history.push 没有被调用。有谁知道我可以做些什么来让这个测试通过?
编辑:事实证明将当前的 jest.mock 替换为:
const history = createMemoryHistory();
const pushSpy = await jest.spyOn(history, "push");
允许我在 history.push
位于 mockFetch 函数之外时调用模拟历史记录......但当它位于函数内部时则不行。这就是我现在想要弄清楚的。
主应用路由:
function App()
const classes = useStyles();
return (
<div className="App">
<Banner />
<div id="mainSection" className=classes.root>
<ErrorBoundary>
<Paper>
<Router history=history>
<Switch>
<Route path="/newaccount">
<NewAccountPage />
</Route>
<Route path="/disqualify">
<DisqualificationPage />
</Route>
<Route path="/">
<LandingPage />
</Route>
</Switch>
</Router>
</Paper>
</ErrorBoundary>
</div>
</div>
);
export default App;
组件:(省略了一些多余的字段)
import React, useCallback, useEffect, useState from "react";
import useSelector, useDispatch from "react-redux";
import useHistory from "react-router-dom";
import Grid from "@material-ui/core/Grid";
import Button from "@material-ui/core/Button";
import TextFieldStyled from "../TextFieldStyled/TextFieldStyled.js";
import * as actionTypes from "../../redux/actions/rootActions.js";
import
checkAllErrors,
from "../../validators/validators.js";
import mockFetch from "../../fetchCall/fetchCall";
import "./LandingPage.css";
const LandingPage = () =>
const dispatch = useDispatch();
const history = useHistory();
const
changeCarPrice,
changeErrorMessage,
resetState,
= actionTypes;
useEffect(() =>
return () =>
dispatch( type: resetState );
;
, [dispatch, resetState]);
const carPrice = useSelector(
(state) => state
);
const handleSubmit = (e) =>
e.preventDefault();
if (!checkAllErrors(allErrors))
// Call the API
mockFetch(carPrice)
.then((response) =>
history.push("/newaccount");
)
.catch((error) =>
dispatch( type: changeErrorMessage, payload: error );
history.push("/disqualify");
);
;
const [allErrors, setAllErrors] = useState(
carValueError: false,
);
return (
<div id="landingPage">
<Grid container>
<Grid item xs=2 />
<Grid item xs=8>
<form onSubmit=handleSubmit>
<Grid container id="internalLandingPageForm" spacing=4>
/* Text Fields */
<Grid item xs=6>
<TextFieldStyled
info="Enter Car Price ($):"
value=carPrice
adornment="$"
type="number"
label="required"
required
error=allErrors.carValueError
id="carPriceField"
helperText=
allErrors.carValueError &&
"Please enter a value below 1,000,000 dollars"
passbackFunction=(e) => handleChange(e, changeCarPrice)
/>
</Grid>
</Grid>
<Grid container id="internalLandingPageFormButton" spacing=4>
<Grid item xs=4 />
<Grid item xs=3>
<Button
variant="contained"
color="primary"
type="submit"
id="applyNowButton"
title="applyNowButton"
onSubmit=handleSubmit
>
Apply Now
</Button>
</Grid>
<Grid item xs=5 />
</Grid>
</form>
</Grid>
<Grid item xs=2 />
</Grid>
</div>
);
;
export default LandingPage;
测试:
const wrapWithRedux = (component) =>
return <Provider store=store>component</Provider>;
;
it("simulates a successful submission form process", () =>
const mockHistoryPush = jest.fn();
jest.mock("react-router-dom", () => (
...jest.requireActual("react-router-dom"),
useHistory: () => (
push: mockHistoryPush,
),
));
render(
wrapWithRedux(
<Router history=history>
<Switch>
<Route path="/newaccount">
<NewAccountPage />
</Route>
<Route path="/">
<LandingPage />
</Route>
</Switch>
</Router>
)
);
const carPriceField= screen.getByTestId("carPriceField");
fireEvent.change(carPriceField, target: value: "5000" );
const buttonSubmission= screen.getByTitle("buttonSubmission");
fireEvent.click(buttonSubmission);
expect(mockHistoryPush).toHaveBeenCalledWith('/newaccount');
);
【问题讨论】:
您能否在if (!checkAllErrors(allErrors)) ...
块内添加console.log
语句以确保它确实调用mockFetch
?
@ArunKumarMohan 刚刚将它添加到 mockFetch 的 .then 中,它确实被调用了。所以代码确实到达了 history.push("/newaccount")
酷。我猜期望在mockFetch
函数返回的承诺解决之前运行。您可以通过注释掉mockFetch
调用并在if
条件中添加history.push
来测试这一点。如果你可以在你的代码中添加一个 CodeSandbox URL,它会更容易调试。
@ArunKumarMohan 嗯,如果我将 history.push 移出 mockFetch 调用,我仍然会遇到与从未调用过的 jest.fn()
相同的错误。我开始认为这就是 history.push
被嘲笑的方式。
我在这方面取得了一些进展。如果我这样做:``` const history = createMemoryHistory(); const pushSpy = jest.spyOn(history, "push"); ``` 然后我可以在 mockFetch 之外访问模拟的 history.push。但如果它在 promise 函数 mockFetch 中......我不能。我认为这与它的承诺有关。
【参考方案1】:
所以在进一步研究了嘲笑历史之后,我从How to mock useHistory hook in jest?找到了这个
答案似乎是做下面的代码然后会调用pushSpy
const history = createMemoryHistory();
const pushSpy = jest.spyOn(history, "push");
render(
wrapWithRedux(
<Router history=history>
<Switch>
<Route path="/newaccount">
<NewAccountPage />
</Route>
<Route path="/">
<LandingPage />
</Route>
</Switch>
</Router>
)
);
我还必须使用 react 测试库中的 waitFor
包装期望行,以获取 history.push
以调用 mockFetch
函数内部的模拟,如下所示:
await waitFor(() => expect(pushSpy).toHaveBeenCalledWith("/newaccount"));
通过这两个修改,现在测试通过了。
【讨论】:
【参考方案2】:我不会模拟一堆东西并测试实现,而是测试行为。
这里有一些想法可能有助于编写行为而不是实现细节。
参考 React Router 测试文档
React Router 有一个基于操作的guide on testing navigation。
劫持 render()
以使用 Redux 进行包装
您可以劫持react-testing-library
的render()
函数来获取干净状态或种子状态,而不是为每个测试套件编写Redux 包装器。 Redux has docs here.
不要嘲笑fetch()
按钮提交获取页面上文本字段的值并将它们传递给 mockFetch
我会使用 HTTP 拦截器来存根响应。通过这种方式,您可以获得异步行为,并将测试绑定到后端,而不是将其绑定到工具。假设你不喜欢fetch()
,你会一直坚持下去,直到你迁移所有东西。我在Testing components that make API calls这个主题上发表了一篇博文。
这是您的代码示例,并进行了一些修改:
it("creates a new account", () => // <-- more descriptive behavior
// Stub the server response
nock(`$yoursite`)
.post('/account/new') // <-- or whatever your backend is
.reply(200);
render(
<MemoryRouter initialEntries=["/"]> // <-- Start where your forms are at
<Switch>
<Route path="/newaccount">
<NewAccountPage />
</Route>
<Route path="/">
<LandingPage />
</Route>
</Switch>
</MemoryRouter>
);
const carPriceField= screen.getByTestId("carPriceField");
fireEvent.change(carPriceField, target: value: "5000" );
const buttonSubmission= screen.getByTitle("buttonSubmission");
fireEvent.click(buttonSubmission);
expect(document.body.textContent).toBe('New Account Page'); // <-- Tests route change
);
【讨论】:
以上是关于我如何访问这个 Promise 调用中的历史对象?的主要内容,如果未能解决你的问题,请参考以下文章
如何修复 React 中的“类型错误:尝试访问对象的属性时无法读取未定义的属性名称”
javascript,promise,如何在 then 范围内访问变量 this [重复]