我正在测试一个具有提交按钮的功能组件,该按钮可以对api进行异步调用。异步调用位于自定义挂钩中。按照标准测试惯例,我已经模拟了该钩子,因此将调用我的模拟,而不是实际的异步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块中,但是为了清楚起见,我将其省略。
在生产中,当按下“提交”按钮时,将发生异步调用,然后应切换状态,从而呈现其他一些组件。我的测试旨在查看是否已经渲染了这些组件(为此,我正在使用React测试库)。
我遇到的问题是,无论我将其放在第一个代码块的占位符中,由于永远不会到达setState块,因此我的测试将始终失败。如果删除await
语句,则会命中setState块,并且因为状态已更改,所以要显示的组件在那里。但是,由于实际的调用是异步的,因此显然这在测试之外无法按预期进行。这是我尝试过的一些无效的方法:
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();
});
答案 0 :(得分:0)
尝试类似的操作
forgotPassword: jest.fn( async email => {
return await new Promise( ( resolve, reject ) => {
if ( email ) {
resolve( email );
} else {
reject( "Error" );
}
} );
} );
如果它不起作用,请告诉我。
答案 1 :(得分:0)
对于遇到相同问题的任何人,我发现了可以解决此问题的三种方法(首选方法是选项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中的回调以告知测试已完成。如果您不调用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()的附加好处,这将有助于使测试警告保持沉默。