我如何访问这个承诺调用内部的历史对象?



在React组件中,按钮提交获取页面上文本字段的值并将它们传递给mockFetch。然后,如果mockFetch的承诺成功,它会导致history.push('/newaccount')被触发。

我设置我的测试点击按钮,然后尝试检测history.push('/newaccount')调用与我传递给它,但我的模拟历史。Push不会被调用。有人知道我该怎么做才能通过这个测试吗?

编辑:原来它取代了当前的笑话。模拟:

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');
});

所以在进一步研究嘲弄历史之后,我发现这是从如何在笑话中模仿useHistory钩子?

答案似乎是执行下面的代码,然后将调用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函数内部的mock,如下所示:

await waitFor(() => expect(pushSpy).toHaveBeenCalledWith("/newaccount"));

经过这两处修改,测试现在通过了。

与其模拟一堆东西和测试实现,不如测试行为。

这里有一些想法可能有助于编写行为而不是实现细节。

参考React Router测试文档

React Router有一个基于动作测试导航的指南。

劫持render()与Redux封装

与其为每个测试套件编写Redux包装器,不如劫持react-testing-libraryrender()函数来获得一个干净的状态或种子状态。Redux有文档在这里。

不要模仿fetch()

按钮提交获取页面上文本字段的值并将它们传递给mockFetch

我会使用HTTP拦截器来存根响应。这样你就得到了异步行为,你将测试绑定到后端,而不是将测试绑定到工具。如果你不喜欢fetch(),你就会被它困住,直到你迁移所有的东西。我写了一篇关于测试调用API的组件的博文。

下面是一些编辑后的代码示例:

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
});

最新更新