如何让Jest等待所有异步代码在期望断言之前完成执行

Dan*_*Dan 27 mocking reactjs jestjs enzyme

我正在为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)
          }
    }

}
Run Code Online (Sandbox Code Playgroud)

当我在测试中执行此操作时,它会失败,因为它会在回调被触发之前执行.

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')
    });
});
Run Code Online (Sandbox Code Playgroud)

我该如何测试?我理解angular用zonejs解决了这个问题,在React中是否有一个等效的方法?

Tad*_*ius 31

这是一个片段,等待待处理的Promise被解决:

const flushPromises = () => new Promise(setImmediate);
Run Code Online (Sandbox Code Playgroud)

请注意,setImmediate是一个非标准功能(预计不会成为标准功能).但是,如果它足以满足您的测试环境,那么应该是一个很好的解决方案.它的描述:

此方法用于分解长时间运行的操作,并在浏览器完成其他操作(如事件和显示更新)后立即运行回调函数.

这里大致是如何使用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 
});
Run Code Online (Sandbox Code Playgroud)

如果你想要一些真实世界的例子,我在这个项目中经常使用它.

  • 对于 jest 27+,您还可以使用 process.nextTick,例如 `await new Promise(process.nextTick);`。 (15认同)
  • 值得注意的是,在 jest 27 (https://github.com/facebook/jest/pull/11222) 中,`setImmediate` 已从 jsdom 中删除,因此这在 jest 测试中不再有效。 (7认同)
  • 这个flushPromises和`await Promise.resolve()`有什么区别?他们如何以不同的方式等待微任务队列?询问是因为当只有一个 Promise 需要履行时我可以使用 `Promise.resolve` ,但否则需要 `flushPromises` 。 (5认同)
  • 这实际上并没有达到问题所要求的目的。这(或者甚至只是“await Promise.resolve()”)将有效地将测试的其余部分放在已经完成的承诺后面的堆栈后面,但它不会等待任何仍需要时间完成的承诺。 (5认同)
  • 很棒!如果您想使它更简洁一些,可以进一步简化该功能:`const flushPromises =()=&gt; new Promise(setImmediate);` (3认同)
  • @EricHaynes 的评论肯定被这个解决方案忽略了。您不能保证在调用“await new Promise(process.nextTick);”时您的承诺会得到履行。 (3认同)

Thi*_*men 10

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

Run Code Online (Sandbox Code Playgroud)