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

问题描述 投票:0回答:8

我正在为 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 中是否有等效的方法?

reactjs mocking jestjs enzyme
8个回答
263
投票

更新了 Jest 27+

对于 jest 27+,您还可以使用 process.nextTick:

await new Promise(process.nextTick);

(感谢评论中的 Adrian Godong)

原答案

这是等待待处理的 Promise 得到解决的代码片段:

const flushPromises = () => new Promise(setImmediate);

请注意,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 
});

如果你想要一些实际的例子,我在这个项目中经常使用这个。


8
投票
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



4
投票
aThingThatReturnsAPromise()

,然后将其导入到您的测试用例中。


由于

aThingThatReturnsAPromise()

返回一个 Promise,您可以利用 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 处理测试用例
此处


3
投票
flush-promises

。下面显示了包含两个测试的示例测试套件(也如引用的网址所示): 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); }); });



2
投票

但是,我可以通过在设置完成后调用 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

runAllImmediates


1
投票
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(); }); });



0
投票
await jest.runAllTimersAsync();

将等待所有超时并承诺解决!

    


-1
投票

await Promise.resolve();

一个更通用的解决方案是用测试库中的 
render

包装您的

await act
函数,如下所示:
let component;
await act(()=>{
    component = render(<Whatever />);
});

此后,在任何 
useEffect

链中创建的所有承诺,甚至是改变状态的

useEffect
(响应
fetch
等),导致更多
useEffect
运行,从而执行更多
fetch
es 或其他 —— 所有的承诺都将得到解决并准备好断言,而无需异步机制,例如测试库的
waitFor
findBy*
等。
    

© www.soinside.com 2019 - 2024. All rights reserved.