gpt4 book ai didi

reactjs - 如何使用 jest 在 React 功能组件中模拟异步调用

转载 作者:行者123 更新时间:2023-11-28 21:35:39 25 4
gpt4 key购买 nike

我正在测试一个功能组件,它有一个提交按钮,可以对 api 进行异步调用。异步调用位于自定义 Hook 中。根据标准测试实践,我模拟了钩子(Hook),这样我的模拟将被调用而不是实际的异步 api:

someComponent.test.js

jest.mock("../../../CustomHooks/user", () => ({
useUser: () => ({
error: null,
loading: false,
forgotPassword: <SOMETHING HERE>

})
}));

我知道调用了我的 forgotPassword 函数,因为当我将它更改为 forgotPassword: "" 时,我在测试中遇到错误,指出 forgotPassword 不是函数.

当我的提交按钮被点击时调用的函数的一个非常简单的表示是这样的:

someComponent.js

import { useUser } from "../../../CustomHooks/user"

const SomeComponent = () => {

....state and other things etc....

const { error, loading, forgotPassword } = useUser()

const submit = async () => {
await forgotPassword(emailValue);
setState(prevState => {
return {
...prevState,
content: "code"
};
});
}

}

注意:我对异步函数 await forgotPassword... 的调用包含在代码中的 try/catch block 中,但为了清楚起见,我将其省略。

在生产中,当按下提交按钮时,会发生异步调用,然后应该切换状态,从而渲染一些其他组件。我的测试会查看这些组件是否已被渲染(为此我正在使用 React 测试库)。

我遇到的问题是,无论我在第一个代码块的占位符中放置什么,我的测试总是会失败,因为永远不会到达 setState block 。如果我删除 await 语句,则会命中 setState block ,并且当状态已更改时我想要显示的组件就在那里。但是,显然这不会在测试之外按预期工作,因为实际调用是异步的。以下是我尝试过但不起作用的一些方法:

DOESN'T WORK

forgotPassword: () => {
return Promise.resolve({ data: {} });
}
DOESN'T WORK

forgotPassword: jest.fn(() => {
return Promise.resolve();
})
DOESN'T WORK

forgotPassword: jest.fn(email => {
return new Promise((resolve, reject) => {
if (email) {
resolve(email);
} else {
reject("Error");
}
});
}),

正如我已经说过的,如果我删除 await 语句,那么状态会改变并且组件出现,因此测试通过。但是,由于显而易见的原因,这不是我想要的。

额外信息

这是我测试的简化版本:

it("changes state/content from email to code when submit clicked", () => {
const { getByTestId, getByText, debug } = render(<RENDER THE COMPONENT>);

const submitButton = getByTestId("fpwSubmitButton");
expect(submitButton).toBeInTheDocument();

const emailInput = getByTestId("fpwEmailInput");

fireEvent.change(emailInput, {
target: { value: "testemail@testemail.com" }
});

fireEvent.click(submitButton);

debug();

THE STATEMENTS BELOW ARE WHERE IT FAILS AS THE STATE DOESN'T CHANGE WHEN AWAIT IS PRESENT

const codeInput = getByTestId("CodeInput");
expect(codeInput).toBeInTheDocument();
});

最佳答案

对于遇到同样问题的任何人,我发现了三种可以解决此问题的方法(首选方法是选项 3)。所有方法都使用一个简单的模拟函数来替换 <SOMETHING HERE>我问题中的第一个代码块。这可以替换为 () => {} :

jest.mock("../../../CustomHooks/user", () => ({
useUser: () => ({
error: null,
loading: false,
forgotPassword: () => {}
})
}));

选项 1

第一种方法是将依赖于异步函数的测试代码包装在 setTimeout 中。用done回调:

it("changes state/content from email to code when submit clicked", done => {
const { getByTestId, debug } = render(<RENDER THE COMPONENT>);

const submitButton = getByTestId("fpwSubmitButton");
expect(submitButton).toBeInTheDocument();

const emailInput = getByTestId("fpwEmailInput");

fireEvent.change(emailInput, {
target: { value: "testemail@testemail.com" }
});

fireEvent.click(submitButton);

setTimeout(() => {
const codeInput = getByTestId("CodeInput");
expect(codeInput).toBeInTheDocument();
done();
});

debug();
});

注意顶行done回调,以及包裹在setTimeout中的测试代码在底部,然后在 setTimeout 中调用回调以告诉 jest 测试已完成。如果你不调用 done回调,测试将失败,因为它会超时。

选项 2

第二种方法是使用一个名为 flushPromises() 的函数:

function flushPromises() {
return new Promise(resolve => setImmediate(resolve));
}

it("changes state/content from email to code when submit clicked", async () => {
const { getByTestId, debug } = render(<RENDER THE COMPONENT>);

const submitButton = getByTestId("fpwSubmitButton");
expect(submitButton).toBeInTheDocument();

const emailInput = getByTestId("fpwEmailInput");

fireEvent.change(emailInput, {
target: { value: "testemail@testemail.com" }
});

fireEvent.click(submitButton);

await flushPromises();

const codeInput = getByTestId("CodeInput");
expect(codeInput).toBeInTheDocument();

debug();
});

注意 flushPromises()功能在顶部,然后调用站点在底部。

选项 3(首选方法)

最后的方法是从 react-testing-library 导入 wait,将你的测试设置为异步,然后 await wait()每当你有异步代码时:

...
import { render, fireEvent, cleanup, wait } from "@testing-library/react";

...

it("changes state/content from email to code when submit clicked", async () => {
const { getByTestId, debug } = render(<RENDER THE COMPONENT>);

const submitButton = getByTestId("fpwSubmitButton");
expect(submitButton).toBeInTheDocument();

const emailInput = getByTestId("fpwEmailInput");

fireEvent.change(emailInput, {
target: { value: "testemail@testemail.com" }
});

fireEvent.click(submitButton);

await wait()

const codeInput = getByTestId("CodeInput");
expect(codeInput).toBeInTheDocument();

debug();
});

所有这些解决方案都有效,因为它们在执行测试代码之前等待下一个事件循环。 Wait()基本上是 flushPromises() 的包装器包括 act() 的额外好处,这将有助于消除测试警告。

关于reactjs - 如何使用 jest 在 React 功能组件中模拟异步调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59050374/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com