【问题标题】:How to make Jest wait for all asynchronous code to finish execution before expecting an assertion如何让 Jest 在等待断言之前等待所有异步代码完成执行
【发布时间】:2017-11-28 04:40:28
【问题描述】:

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


【解决方案1】:

为 Jest 27+ 更新

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

await new Promise(process.nextTick);

(感谢 cmets 的 Adrian Godong)

原答案

这是一个等待待处理的 Promise 解决的 sn-p:

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 
});

如果你想要一些实际的工作示例,我经常使用 in this project

【讨论】:

  • 效果很好!如果您想让它更简洁一点,您可以进一步简化该功能:const flushPromises = () =&gt; new Promise(setImmediate);
  • 这个flushPromises和await Promise.resolve()有什么区别?他们如何以不同的方式等待微任务队列?问是因为我可以使用Promise.resolve 当只有一个 Promise 需要完成时,但flushPromises 是必需的。
  • 这个功能确实有帮助,但我很快就明白了为什么它没有等待。处理承诺的那行开头没有await。就我而言,这不会影响程序的正确性,但这可能不是每个人的最佳解决方案。
  • 这实际上并没有满足问题的要求。这(甚至只是await Promise.resolve())将有效地将测试的其余部分放在已经实现的承诺后面的堆栈后面,但它不会等待任何仍需要时间完成的承诺。
  • 值得注意的是 setImmediate 在 jest 27 (github.com/facebook/jest/pull/11222) 中已从 jsdom 中删除,因此这在 jest 测试中将不再起作用。
【解决方案2】:

我建议您从其模块或文件中导出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 处理测试用例 here

【讨论】:

    【解决方案3】:

    作为其他答案中列出的某些技术的替代方法,您还可以使用 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);
    
        });
    
    });
    

    【讨论】:

    • 请注意,flush-promises 模块仅使用accepted answer 中的const flushPromises = () =&gt; new Promise(setImmediate) 技术(如果setImmediate 不存在,则回退到setTimeout)。
    【解决方案4】:

    我不知道任何 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 的 runAllTicksrunAllImmediates 可能值得关注。

    【讨论】:

      【解决方案5】:

      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
      
      

      【讨论】:

        【解决方案6】:

        虽然可以重构伪代码以遵循 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();
            });
        });
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-03-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-02-22
          • 2017-02-02
          相关资源
          最近更新 更多