React Testing测试axios post成功/失败场景报错排查
React表单单元测试失败排查与修复
问题背景
业务代码运行正常,可实现将用户在表单中输入的数据通过axios提交至json-server的功能,但使用msw搭建mock服务编写的单元测试始终执行失败,覆盖post请求成功、失败的两个场景均无法拿到对应的提示文本。
测试代码
describe("Testing Booking component post http call", () => { const server = setupServer( rest.post("http://localhost:4000/bookings", (req, res, ctx) => { return res( ctx.json({ bookedOn: "2022-03-25", buffetName: "SouthIndianFestivalSpecial", emailId: "kavya@gmail.com", id: 1018, plateCount: "2", }) ); }) ); beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); it("Testing axios post call success", async () => { render(<BookingComponent />); let buffetName = screen.getByTestId("buffetName"); fireEvent.change(buffetName, { target: { value: "SouthIndianFestivalSpecial" }, }); let email = screen.getByTestId("emailId"); fireEvent.change(email, { target: { value: "kavya@gmail.com" } }); let plateCount = screen.getByTestId("plateCount"); fireEvent.change(plateCount, { target: { value: 2 } }); let bookedOnDate = screen.getByTestId("bookedOn"); fireEvent.change(bookedOnDate, { target: { value: "2022-03-25" } }); let form = screen.getByTestId("buffet-form"); fireEvent.submit(form); let successMessage = await screen.findByTestId("success"); expect(successMessage.innerHTML).toContain(`1018`); }); it("Testing axios post call error", async () => { server.use( rest.post("http://localhost:4000/bookings", (req, res, ctx) => { return res(ctx.status(500)); }) ); render(<BookingComponent />); let buffetName = screen.getByTestId("buffetName"); fireEvent.change(buffetName, { target: { value: "SouthIndianFestivalSpecial" }, }); let email = screen.getByTestId("emailId"); fireEvent.change(email, { target: { value: "kavya@gmail.com" } }); let plateCount = screen.getByTestId("plateCount"); fireEvent.change(plateCount, { target: { value: 2 } }); let bookedOnDate = screen.getByTestId("bookedOn"); fireEvent.change(bookedOnDate, { target: { value: "2022-03-25" } }); let form = screen.getByTestId("buffet-form"); fireEvent.submit(form); let errorMessage = await screen.findByTestId("error"); expect(errorMessage.innerHTML).toContain("Something went wrong"); }); });
业务提交逻辑代码
console.log("buffet button clicked"); // 1. 该方法会在用户点击'Book Buffet'按钮时触发 // 2. 提交时需要阻止页面重载 // 3. 校验所有表单字段是否已填写,若存在未填项则将mandatory状态设为true // 4. 若所有字段均已填写,则向"http://localhost:4000/bookings/"发起axios调用,传入对应state作为请求数据 // 5. 若axios调用成功,将如下字符串赋值给successMessage状态: // "Booking is successfully created with bookingId: " + <id> // 6. 若axios调用失败,将错误消息赋值为"Something went wrong" e.preventDefault(); let allfiesds = formErrors.emailIdError && formErrors.bookedOnError && formErrors.buffetNameError && formErrors.plateCountError; setMandatory(allfiesds ? true : false); // 功能正常但测试无法通过 if (valid) { axios .post(url, state) .then((res) => { console.log(res.data); setSuccessMessage( "Booking is successfully created with bookingId: " + res.data.id ); setErrorMessage(""); console.log(res.data.id); }) .catch((error) => { if (error.response) { console.log(error.response.data.message); } setErrorMessage(messages.ERROR); setSuccessMessage(""); console.log(); }); }
报错信息
Testing Booking component post http call › Testing axios post call success expect(received).toContain(expected) // indexOf Expected substring: "1018" Received string: "" 203 | fireEvent.submit(form); 204 | let successMessage = await screen.findByTestId("success"); > 205 | expect(successMessage.innerHTML).toContain(`1018`); | ^ 206 | }); 207 | it("Testing axios post call error", async () => { 208 | server.use( at Object.<anonymous> (src/App.test.js:205:38) ● Testing Booking component post http call › Testing axios post call error expect(received).toContain(expected) // indexOf Expected substring: "Something went wrong" Received string: "" 227 | fireEvent.submit(form); 228 | let errorMessage = await screen.findByTestId("error"); > 229 | expect(errorMessage.innerHTML).toContain("Something went wrong"); | ^ 230 | }); 231 | }); 232 | at Object.<anonymous> (src/App.test.js:229:36)
故障原因
两个用例都能定位到success/error对应的DOM节点,但节点内容为空,说明提交时根本没有发起axios请求,问题出在三个地方:
valid变量未定义:代码中只计算了拼写错误的allfiesds变量,没有给if判断用到的valid赋值,导致valid始终为falsy值,永远进不到发请求的分支- 校验逻辑写反:现有判断
formErrors.xxxError &&的含义是所有字段都有错时才判定为需要提示必填,和需求完全相反 - 地址不匹配:注释里业务请求地址末尾带斜杠,msw拦截的地址不带斜杠,会导致mock拦截失效
修改方案
- 修正表单校验逻辑,替换原有校验部分代码:
e.preventDefault(); // 所有字段无错误时判定表单校验通过 const isFormValid = !formErrors.emailIdError && !formErrors.bookedOnError && !formErrors.buffetNameError && !formErrors.plateCountError; // 校验不通过时显示必填提示 setMandatory(!isFormValid); if (isFormValid) { // 保留原有axios请求逻辑,注意统一url地址 axios.post("http://localhost:4000/bookings", state) .then((res) => { setSuccessMessage( "Booking is successfully created with bookingId: " + res.data.id ); setErrorMessage(""); }) .catch((error) => { setErrorMessage("Something went wrong"); setSuccessMessage(""); }); }
- 检查表单onChange逻辑,确认输入合法时会将对应
formErrors的错误标识置为false,避免校验一直不通过 - 保证业务代码的请求地址和msw配置的拦截地址完全一致,不要出现末尾斜杠差异
内容的提问来源于stack exchange,提问作者Ashutosh Nagrale
相关产品推荐
相关产品推荐

