我正在尝试为需要在其componentWillMount
方法中完成异步操作的React组件编写测试。 componentWillMount
调用一个函数,作为prop传递,它返回一个promise,我在我的测试中模拟了这个函数。
这样可以正常工作,但是如果测试在调用setImmediate
或process.nextTick
时失败,则Jest不会处理该异常并且它会过早退出。下面,您可以看到我甚至尝试捕获此异常,但无济于事。
如何在Jest中使用setImmediate
或nextTick
之类的内容?这个问题的公认答案是我试图实施失败的原因:React Enzyme - Test `componentDidMount` Async Call。
it('should render with container class after getting payload', (done) => {
let resolveGetPayload;
let getPayload = function() {
return new Promise(function (resolve, reject) {
resolveGetPayload = resolve;
});
}
const enzymeWrapper = mount(<MyComponent getPayload={getPayload} />);
resolveGetPayload({
fullname: 'Alex Paterson'
});
try {
// setImmediate(() => {
process.nextTick(() => {
expect(enzymeWrapper.hasClass('container')).not.toBe(true); // Should and does fail
done();
});
} catch (e) {
console.log(e); // Never makes it here
done(e);
}
});
Jest v18.1.0
节点v6.9.1
答案 0 :(得分:2)
有些事情需要注意;
process.nextTick
是异步,因此try / catch无法捕获它。Promise
也会解析/拒绝异步。试一试
it('should render with container class after getting payload', (done) => {
const getPayload = Promise.resolve({
fullname: 'Alex Paterson'
});
const enzymeWrapper = mount(<MyComponent getPayload={getPayload} />);
process.nextTick(() => {
try {
expect(enzymeWrapper.hasClass('container')).not.toBe(true);
} catch (e) {
return done(e);
}
done();
});
});
答案 1 :(得分:2)
以下一种方式克服该问题atm(也解决了Enzyme和componentDidMount和async setState中的异步调用问题):
it('should render proper number of messages based on itemsPerPortion', (done) => {
const component = shallow(<PublishedMessages itemsPerPortion={2} messagesStore={mockMessagesStore()} />);
setImmediate(() => { // <-- that solves async setState in componentDidMount
component.update();
try { // <-- that solves Jest crash
expect(component.find('.item').length).toBe(2);
} catch (e) {
return fail(e);
}
done();
});
});
(酶3.2.0,Jest 21.1.6)
<强>更新强>
使用async / await(并且它仍在解决async componentDidMount和async setState)时,想出了另一个更好(但仍然很奇怪)的解决方案:
it('should render proper number of messages based on itemsPerPortion', async () => {
// Magic below is in "await", looks as that allows componentDidMount and async setState to complete
const component = await shallow(<PublishedMessages itemsPerPortion={2} messagesStore={mockMessagesStore()} />);
component.update(); // still needed
expect(component.find('.item').length).toBe(2);
});
其他与异步相关的操作也应该以{{1}}为前缀,以及
await
答案 2 :(得分:1)
继弗拉基米尔的回答+编辑之后,这里有一个对我有用的替代方案。而不是await
装载,await
wrapper.update()
:
it('...', async () => {
let initialValue;
let mountedValue;
const wrapper = shallow(<Component {...props} />);
initialValue = wrapper.state().value;
await wrapper.update(); // componentDidMount containing async function fires
mountedValue = wrapper.state().value;
expect(mountedValue).not.toBe(initialValue);
});
答案 3 :(得分:1)
另一个可能更清洁的解决方案,使用async / await并利用jest / mocha的功能来检测返回的诺言:
function currentEventLoopEnd() {
return new Promise(resolve => setImmediate(resolve));
}
it('should render with container class after getting payload', async () => {
let resolveGetPayload;
let getPayload = function() {
return new Promise(function (resolve, reject) {
resolveGetPayload = resolve;
});
}
const enzymeWrapper = mount(<MyComponent getPayload={getPayload} />);
resolveGetPayload({
fullname: 'Alex Paterson'
});
await currentEventLoopEnd(); // <-- clean and clear !
expect(enzymeWrapper.hasClass('container')).not.toBe(true);
});
答案 4 :(得分:0)
- 发布作为答案的人无法在评论中格式化代码块。 -
在弗拉基米尔的回答基础上,请注意使用async / await也可以在beforeEach中使用:
var wrapper
beforeEach(async () => {
// Let's say Foobar's componentDidMount triggers async API call(s)
// resolved in a single Promise (use Promise.all for multiple calls).
wrapper = await shallow(<Foobar />)
})
it('does something', () => {
// no need to use an async test anymore!
expect(wrapper.state().asynchronouslyLoadedData).toEqual(…)
})
答案 5 :(得分:0)
如其他人所示,将传递给process.nextTick
/ setImmediate
的回调块包装为try
/ catch
是可行的,但这很冗长且分散注意力。
一种更清洁的方法是使用await new Promise(setImmediate);
测试回调内的简短行async
来刷新承诺。这是一个使用此示例的示例,它使useEffect
中的HTTP请求(对componentDidMount
同样有用)在运行断言之前解析并触发重新渲染:
LatestGist.js
):import axios from "axios";
import {useState, useEffect} from "react";
export default () => {
const [gists, setGists] = useState([]);
useEffect(() => getGists(), []);
const getGists = async () => {
const res = await axios.get("https://api.github.com/gists");
setGists(res.data);
};
return (
<>
{gists.length
? <div data-test="test-latest-gist">
the latest gist was made on {gists[0].created_at} by {gists[0].owner.login}
</div>
: <div>loading...</div>}
</>
);
};
LatestGist.test.js
):import React from "react";
import Enzyme, {mount} from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({adapter: new Adapter()});
import mockAxios from "axios";
import LatestGist from "./LatestGist";
jest.mock("axios");
describe("LatestGist", () => {
beforeEach(() => jest.resetAllMocks());
it("should load the latest gist", async () => {
mockAxios.get.mockImplementationOnce(() => Promise.resolve({
data: [{owner: {login: "test name"}, created_at: "some date"}],
status: 200
}));
const wrapper = mount(<LatestGist />);
let gist = wrapper.find('[data-test="test-latest-gist"]');
expect(gist.exists()).toBe(false);
await new Promise(setImmediate);
wrapper.update();
expect(mockAxios.get).toHaveBeenCalledTimes(1);
gist = wrapper.find('[data-test="test-latest-gist"]');
expect(gist.exists()).toBe(true);
expect(gist.text()).toContain("test name");
expect(gist.text()).toContain("some date");
});
});
使用expect(gist.text()).toContain("foobar");
这样的行强制失败的声明不会导致套件崩溃:
● LatestGist › should load the latest gist
expect(string).toContain(value)
Expected string:
"the latest gist was made on some date by test name"
To contain value:
"foobar"
at Object.it (src/LatestGist.test.js:30:25)