酶无法反映异步setState回调内部的setState变化

时间:2019-11-28 01:01:02

标签: reactjs unit-testing jestjs enzyme

我想整天解决这个问题,但无法正常工作。

我有一个用于更新componentDidMount内部状态的组件,然后在setState's回调中,我再次更新状态,但是回调异步运行。这是因为我在其中发出一个http请求,并使用其响应来更新状态。在单元测试期间,我模拟了此模块,因此它无法到达网络。但是,酶不会在异步回调中反映状态更改。这是有道理的,因为它是异步的,但是我还没有找到解决方法。

我已经建立了我想要达到的目标的缩小版本。

这是我的组件:

class Component extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            value: 0,
        };
        this._isMounted = false;
    }

    componentDidMount() {
        this._isMounted = true;
        this.setState(state => {
            const { value } = this.state;
            return {
                ...state,
                value: value + 15,
            };
        }, async () => {
            try {
                // If you remove this line, it works. 
                await Promise.resolve(15);
                if (this._isMounted) {
                    this.setState(state => {
                        const { value } = this.state;
                        // If you console.log during testing,
                        // this value is updated correctly, but Enzyme doesn't reflect it.
                        return {
                            ...state,
                            value: value + 15,
                        };
                    });
                }
            } catch(e) {
                // Do nothing...
            }
        });
    }

    componentWillUnmount() {
        this._isMounted = false;
    }

    render() {
        const { value } = this.state;
        return (
            <div>{ value } </div>
        );
    }
}

这是我的考验:

describe('Component Enzyme Bug Testing', () => {
    it('should update value properly', () => {
        const wrapper = shallow(<Component />, { disableLifecycleMethods: true });
        wrapper.instance().componentDidMount();
        expect(wrapper.state().value).toEqual(30);
    });
});

在测试运行中,state().value为15,预期值为30。这意味着它记录了第一个更改,但没有记录第二个更改。

1 个答案:

答案 0 :(得分:1)

在Node中执行IO绑定代码时,这些操作的回调不在当前上下文中运行,而是将它们放回到事件循环(more info)中,然后在下一个“滴答”中执行。这实际上意味着,当您运行调用异步代码的同步代码时,即使该异步代码实际上不是 异步(例如Promise.resolve),它仍将在之后运行同步代码已运行完毕。

在您的情况下,正如您在代码注释中提到的那样,只有在引入Promise.resolve时测试才开始失败,这是因为在这一点上,我们将状态更改转移到其他上下文,然后测试运行到完成并断言失败(如您所希望的那样,现在知道为什么)。

要修复测试,这非常简单,因为知道状态更新现在将在下一个刻度执行,所以我们只想安排断言来执行相同的操作:

it('should update value properly', done => {
  const wrapper = shallow(<Component />, { disableLifecycleMethods: true });
  wrapper.instance().componentDidMount();
  process.nextTick(() => {
    expect(wrapper.state().value).toEqual(30);
    done();
  })
});

这会将断言与状态更新运行在相同的滴答声上,并且根据文章,我们知道事件循环阶段遵循FIFO模型,因此我们可以保证断言将在状态更新后运行。