如何让 Jest 在等待断言之前等待所有异步代码完成执行
How to make Jest wait for all asynchronous code to finish execution before expecting an assertion
我正在为 React 应用程序编写集成测试,即一起测试许多组件的测试,我想模拟对外部服务的任何调用。
问题是测试似乎在执行异步回调之前执行,导致我的测试失败。
这附近有没有?我能以某种方式等待调用异步代码完成吗?
这是一些糟糕的伪代码来说明我的观点。
我想测试当我挂载 Parent 时,它的 Child 组件呈现从外部服务返回的内容,我将模拟它。
class Parent extends component
{
render ()
{
<div>
<Child />
</div>
}
}
class Child extends component
{
DoStuff()
{
aThingThatReturnsAPromise().then((result) => {
Store.Result = result
})
}
render()
{
DoStuff()
return(<div>{Store.Result}</div>)
}
}
function aThingThatReturnsAPromise()
{
return new Promise(resolve =>{
eternalService.doSomething(function callback(result) {
resolve(result)
}
}
}
当我在测试中执行此操作时,它失败了,因为它在触发回调之前执行。
jest.mock('eternalService', () => {
return jest.fn(() => {
return { doSomething: jest.fn((cb) => cb('fakeReturnValue');
});
});
describe('When rendering Parent', () => {
var parent;
beforeAll(() => {
parent = mount(<Parent />)
});
it('should display Child with response of the service', () => {
expect(parent.html()).toMatch('fakeReturnValue')
});
});
我该如何测试?我知道 angular 使用 zonejs 解决了这个问题,React 中是否有等效的方法?
我不知道 React 有什么本机的东西可以完成你正在寻找的东西。
但是,我能够通过在设置完成后调用 beforeAll() 的 @done 以类似的代码完成此操作。请在下方查看您的代码更改:
let setupComplete;
jest.mock('eternalService', () => {
return jest.fn(() => {
return { doSomething: jest.fn((cb) => { cb('fakeReturnValue'); setupComplete(); }) };
});
.
.
.
beforeAll(done => {
parent = mount(<Parent />)
setupComplete = done;
});
});
我从未使用过它们,但 Jest 的 runAllTicks and runAllImmediates.
可能会引起我的兴趣
虽然可以重构伪代码以遵循 React 生命周期(使用 componentWillMount()
componentDidMount()
,但测试起来会容易得多。但是,下面是我未经测试的伪代码,用于更改您的测试代码,欢迎大家测试和更新,希望对您有所帮助!
describe('When rendering Parent', () => {
it('should display Child with the response of the service', function(done) => {
const parent = mount(<Parent />);
expect(parent.html()).toMatch('fakeReturnValue');
done();
});
});
更新为 Jest 27+
对于 jest 27+,您还可以使用 process.nextTick:
await new Promise(process.nextTick);
(感谢 Adrian Godong 的评论)
原答案
这是一个等待挂起的 Promise 被解决的片段:
const flushPromises = () => new Promise(setImmediate);
请注意,setImmediate 是一项非标准功能(预计不会成为标准功能)。但如果它足以满足您的测试环境,应该是一个很好的解决方案。它的描述:
This method is used to break up long running operations and run a callback function immediately after the browser has completed other operations such as events and display updates.
以下是使用async/await的大致使用方法:
it('is an example using flushPromises', async () => {
const wrapper = mount(<App/>);
await flushPromises();
wrapper.update(); // In my experience, Enzyme didn't always facilitate component updates based on state changes resulting from Promises -- hence this forced re-render
// make assertions
});
如果你想要一些真实世界的例子,我经常使用这个in this project。
我建议您从其模块或文件中导出 aThingThatReturnsAPromise()
,然后将其导入到您的测试用例中。
因为 aThingThatReturnsAPromise()
returns 一个承诺,你可以利用 Jest 的异步测试功能。 Jest 将等待您的承诺得到解决,然后您可以做出断言。
describe('When rendering Parent', () => {
var parent;
beforeAll(() => {
parent = mount(<Parent />)
});
it('should display Child with response of the service', () => {
expect.assertions(1);
return aThingThatReturnsAPromise().then( () => {
expect(parent.html()).toMatch('fakeReturnValue');
});
});
});
有关详细信息,请阅读 Jest 文档中的 Jest 如何使用 Promises 处理测试用例 here
作为其他答案中列出的一些技术的替代方法,您还可以使用 npm 模块 flush-promises。下面显示了一个包含两个测试的示例测试套件(在引用的 url 中也显示):
const flushPromises = require('flush-promises');
describe('Async Promise Test Suite', () => {
it('A test involving flushPromises', async () => {
const wrapper = mount(<App/>);
await flushPromises();
// more code
});
it('Will not work correctly without flushing promises', async () => {
let a;
let b;
Promise.resolve().then(() => {
a = 1;
}).then(() => {
b = 2;
})
await flushPromises();
expect(a).toBe(1);
expect(b).toBe(2);
});
});
flushPromises
方法在某些情况下已损坏。
只需使用 await Promise.resolve()
代替:
const component = mount(<App/>);
component.find('<button>').simulate('click');
// State changes
await Promise.resolve();
// Assert changes that occurred on the component
我正在为 React 应用程序编写集成测试,即一起测试许多组件的测试,我想模拟对外部服务的任何调用。
问题是测试似乎在执行异步回调之前执行,导致我的测试失败。
这附近有没有?我能以某种方式等待调用异步代码完成吗?
这是一些糟糕的伪代码来说明我的观点。
我想测试当我挂载 Parent 时,它的 Child 组件呈现从外部服务返回的内容,我将模拟它。
class Parent extends component
{
render ()
{
<div>
<Child />
</div>
}
}
class Child extends component
{
DoStuff()
{
aThingThatReturnsAPromise().then((result) => {
Store.Result = result
})
}
render()
{
DoStuff()
return(<div>{Store.Result}</div>)
}
}
function aThingThatReturnsAPromise()
{
return new Promise(resolve =>{
eternalService.doSomething(function callback(result) {
resolve(result)
}
}
}
当我在测试中执行此操作时,它失败了,因为它在触发回调之前执行。
jest.mock('eternalService', () => {
return jest.fn(() => {
return { doSomething: jest.fn((cb) => cb('fakeReturnValue');
});
});
describe('When rendering Parent', () => {
var parent;
beforeAll(() => {
parent = mount(<Parent />)
});
it('should display Child with response of the service', () => {
expect(parent.html()).toMatch('fakeReturnValue')
});
});
我该如何测试?我知道 angular 使用 zonejs 解决了这个问题,React 中是否有等效的方法?
我不知道 React 有什么本机的东西可以完成你正在寻找的东西。
但是,我能够通过在设置完成后调用 beforeAll() 的 @done 以类似的代码完成此操作。请在下方查看您的代码更改:
let setupComplete;
jest.mock('eternalService', () => {
return jest.fn(() => {
return { doSomething: jest.fn((cb) => { cb('fakeReturnValue'); setupComplete(); }) };
});
.
.
.
beforeAll(done => {
parent = mount(<Parent />)
setupComplete = done;
});
});
我从未使用过它们,但 Jest 的 runAllTicks and runAllImmediates.
可能会引起我的兴趣虽然可以重构伪代码以遵循 React 生命周期(使用 componentWillMount()
componentDidMount()
,但测试起来会容易得多。但是,下面是我未经测试的伪代码,用于更改您的测试代码,欢迎大家测试和更新,希望对您有所帮助!
describe('When rendering Parent', () => {
it('should display Child with the response of the service', function(done) => {
const parent = mount(<Parent />);
expect(parent.html()).toMatch('fakeReturnValue');
done();
});
});
更新为 Jest 27+
对于 jest 27+,您还可以使用 process.nextTick:
await new Promise(process.nextTick);
(感谢 Adrian Godong 的评论)
原答案
这是一个等待挂起的 Promise 被解决的片段:
const flushPromises = () => new Promise(setImmediate);
请注意,setImmediate 是一项非标准功能(预计不会成为标准功能)。但如果它足以满足您的测试环境,应该是一个很好的解决方案。它的描述:
This method is used to break up long running operations and run a callback function immediately after the browser has completed other operations such as events and display updates.
以下是使用async/await的大致使用方法:
it('is an example using flushPromises', async () => {
const wrapper = mount(<App/>);
await flushPromises();
wrapper.update(); // In my experience, Enzyme didn't always facilitate component updates based on state changes resulting from Promises -- hence this forced re-render
// make assertions
});
如果你想要一些真实世界的例子,我经常使用这个in this project。
我建议您从其模块或文件中导出 aThingThatReturnsAPromise()
,然后将其导入到您的测试用例中。
因为 aThingThatReturnsAPromise()
returns 一个承诺,你可以利用 Jest 的异步测试功能。 Jest 将等待您的承诺得到解决,然后您可以做出断言。
describe('When rendering Parent', () => {
var parent;
beforeAll(() => {
parent = mount(<Parent />)
});
it('should display Child with response of the service', () => {
expect.assertions(1);
return aThingThatReturnsAPromise().then( () => {
expect(parent.html()).toMatch('fakeReturnValue');
});
});
});
有关详细信息,请阅读 Jest 文档中的 Jest 如何使用 Promises 处理测试用例 here
作为其他答案中列出的一些技术的替代方法,您还可以使用 npm 模块 flush-promises。下面显示了一个包含两个测试的示例测试套件(在引用的 url 中也显示):
const flushPromises = require('flush-promises');
describe('Async Promise Test Suite', () => {
it('A test involving flushPromises', async () => {
const wrapper = mount(<App/>);
await flushPromises();
// more code
});
it('Will not work correctly without flushing promises', async () => {
let a;
let b;
Promise.resolve().then(() => {
a = 1;
}).then(() => {
b = 2;
})
await flushPromises();
expect(a).toBe(1);
expect(b).toBe(2);
});
});
flushPromises
方法在某些情况下已损坏。
只需使用 await Promise.resolve()
代替:
const component = mount(<App/>);
component.find('<button>').simulate('click');
// State changes
await Promise.resolve();
// Assert changes that occurred on the component