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

时间:2017-06-24 21:23:03

标签: reactjs mocking 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)
          }
    }

}

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

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中是否有相同的方法?

7 个答案:

答案 0 :(得分:13)

下面是一个片段,等待解决待解决的Promises:

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

答案 1 :(得分:1)

我没有意识到React原生的东西可以完成你正在寻找的东西。

但是,我可以通过在安装完成后调用beforeAll()的@done来完成相似的代码。请参阅以下代码更改:

&#13;
&#13;
let setupComplete;
jest.mock('eternalService', () => {
    return jest.fn(() => {
        return { doSomething: jest.fn((cb) => { cb('fakeReturnValue'); setupComplete(); }) };
});
.
.
.
    beforeAll(done => {
        parent = mount(<Parent />)
        setupComplete = done;
    });
});
&#13;
&#13;
&#13;

我从未使用过它们,但可能感兴趣的是Jest的runAllTicksrunAllImmediates

答案 2 :(得分:1)

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

答案 3 :(得分:1)

JavaScript的异步性质是单元测试JavaScript花了这么长时间才能成熟的主要原因。 Jest提供了许多用于测试异步代码的模式,如下所述:http://jestjs.io/docs/en/asynchronous.html我最广泛地使用async / await,因为它可以进行非常易读的测试。

但是,由于您正在模拟异步方法,因此不需要任何异步代码。见下文。

let parent
let eternalService = jest.fn(() => 'fakeReturnValue');

beforeEach(() => {
    parent = mount(<Parent />)
    parent.instance().externalService = externalService
})

describe('When rendering Parent', () => {
    it('should display Child with response of the service', () => {
        expect(parent.html()).toMatch('fakeReturnValue')
    });
});

答案 4 :(得分:1)

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

由于aThingThatReturnsAPromise()返回了一个承诺,因此您可以利用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文档here

中了解Jest如何使用Promises处理测试用例。

答案 5 :(得分:1)

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

    });

});

答案 6 :(得分:0)

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