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

IT技术 reactjs mocking jestjs enzyme
2021-04-04 01:24:26

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

6个回答

这是一个等待挂起的 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 
});

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

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

我建议您aThingThatReturnsAPromise()从其module或文件中导出,然后将其导入到您的测试用例中。

由于aThingThatReturnsAPromise()返回Promise,您可以利用 Jest 的异步测试功能。Jest 会等待你的Promise得到解决,然后你就可以做出你的断言。

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 处理测试用例

作为其他答案中列出的某些技术的替代方法,您还可以使用 npm moduleflush-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 module仅使用已接受答案中const flushPromises = () => new Promise(setImmediate)技术(回退到if不存在)。setTimeoutsetImmediate
2021-05-27 01:24:26

我不知道 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 的runAllTicksrunAllImmedates

这种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