首页 > 解决方案 > 酶不反映异步 setState 回调中的 setState 更改

问题描述

我试图弄清楚这一天,但我无法让它发挥作用。

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

我已经构建了我想要实现的缩小版本。

这是我的组件:

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。这意味着它注册了第一个更改,但没有注册第二个更改。

标签: reactjsunit-testingjestjsenzyme

解决方案


当在 Node 中执行 IO 绑定代码时,这些操作的回调不会在当前上下文中运行,它们被放回事件循环(更多信息),然后在下一个“tick”时执行。这实际上意味着,当您运行调用异步代码的同步代码时,即使异步代码实际上不是异步的(例如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 模型,因此我们可以保证断言将在状态更新后运行。


推荐阅读