React + Jest - 测试异步组件并等待挂载

时间:2018-06-03 14:00:49

标签: javascript reactjs jestjs enzyme

我正在尝试测试具有异步componentDidMount

的React组件

承诺本身不需要被嘲笑,它不一定是访问外部内容,大多只是道具的包装。

但是,为了测试它,我需要使用wrapper.update() 4次,这对我来说似乎很奇怪。

解决方案:

一切都不适合我。

这是我的测试的样子(目前有效,但这个解决方案根本不优雅,而且不太可扩展):

import * as React from 'react'
import { shallow, mount } from 'enzyme'
import LargeSelector from './LargeSelector'

describe('<LargeSelector />', async () => {
    const componentDidMountSpy = jest.spyOn(LargeSelector.prototype, 'componentDidMount')

    describe('search', async () => {
        it('should save initial response in cache', async () => {
            const wrapper = await shallow(<LargeSelector query={async (search) => ['search:' + search]} />)

            // WHY DO I NEED 4 UPDATES???
            await wrapper.update()
            await wrapper.update()
            await wrapper.update()
            await wrapper.update()

            expect(LargeSelector.prototype.componentDidMount).toHaveBeenCalledTimes(1) // works fine
            // these 2 only pass the expectation if I call wrapper.update() no less than 4 times    
            expect(wrapper.state()).toHaveProperty('options', ['search:'])
            expect(wrapper.state()).toHaveProperty('initialOptions', ['search:'])
        })
    })
})

以下是componentDidMountfilterResults的实现(在前者中调用):

public async componentDidMount() {
    if (this.props.searchOnInit) {
        const results = await this.filterResults('', [])
        if (this.props.cacheInitialResponse) {
            this.setState({ initialOptions: results })
        }
    }
}

private async filterResults(search: string, filters: IFilter[]) {
    const results = await this.props.query(search, filters)
    this.setState({ options: results })
    return results
}

3 个答案:

答案 0 :(得分:1)

奇怪的行为可能是因为您使用了async来实现componentDidMount

目前,所有React渲染过程都是同步的,因此与渲染流相关的所有内容都需要视为同步。目前也是如此,React团队正在开发一种突破性更改功能,以允许async rendering

但是!即使在此功能可用之后,请记住componentDidMount lifecycle hook仍然是同步的,而其他所有其他人也会挂钩,因此请务必注意React不会等待任何Promise内部解析钩子。

如果符合您的使用案例,您可以在componentDidMount内启动Promise,并让解析后的结果更改状态。但是生命周期钩子会在它结束之前完成,这会影响你的测试用例,因为测试需要等待解析的Promise的结果在断言之前被处理,你可以使用jest.runAllTicks()来保证这种行为。

答案 1 :(得分:1)

我正面临着完全相同的问题。 问题在于,测试不会等待承诺实现。我的解决方案是使用Jest提供的done回调作为测试结束的信号。

赞:

it('wait async code before assert something', (doneCallback) => {
    const wrapper = shallow(<Component />);

    setImediate(() => {
        expect(wrapper.find('.async').length).toBe(1);
        doneCallback();
    });
});

答案 2 :(得分:1)

enzyme-async-helpers在解决此类问题方面给了我很多帮助。
您可以通过添加loading状态然后执行类似的操作来轻松使其工作:

import * as React from 'react';
import { shallow, mount } from 'enzyme';
import LargeSelector from './LargeSelector';
import { waitForState } from 'enzyme-async-helpers';

describe('<LargeSelector />', async () => {

  describe('search', async () => {
    it('should save initial response in cache', async () => {
      const wrapper = await shallow(<LargeSelector query={async (search) => ['search:' + search]} />);

      await waitForState(wrapper, state => state.loading === false);

      expect(LargeSelector.prototype.componentDidMount).toHaveBeenCalledTimes(1); 
      expect(wrapper.state()).toHaveProperty('options', ['search:']);
      expect(wrapper.state()).toHaveProperty('initialOptions', ['search:']);
    });
  });
});

和:
this.setState({ initialOptions: results })
必须更新为:
this.setState({ initialOptions: results, loading: false })