当在Promise中调用setState时,React Jest测试失败

时间:2017-04-28 22:35:12

标签: javascript reactjs unit-testing jestjs enzyme

我正在尝试模拟一个返回promises的服务,以便我可以验证它是否使用正确的参数调用。调用服务的方式因state而异,第一次调用该服务会设置state

在promise中设置状态时,它不会更新,除非我将断言包装在setTimeout中或完全删除了promise。有没有办法用一个简单的承诺和期望来做到这一点?

我的组件:

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {results: []};
    this.service = props.service;
    this.load = this.load.bind(this);
  }

  load() {
    if (this.state.results.length === 0) {
      this.service.load('state is empty')
                  .then(result => this.setState({results: result.data}));
    } else {
      this.service.load('state is nonempty')
                  .then(result => this.setState({results: result.data}));
    }
  }

  render() {
    return (
      <div className="App">
        <button id="submit" onClick={this.load}/>
      </div>
    );
  }
}

我的测试:

it('Calls service differently based on results', () => {

  const mockLoad = jest.fn((text) => {
    return new Promise((resolve, reject) => {
      resolve({data: [1, 2]});
    });
  });

  const serviceStub = {load: mockLoad};

  let component = mount(<App service={serviceStub}/>);
  let button = component.find("#submit");

  button.simulate('click');

  expect(mockLoad).toBeCalledWith('state is empty');

  button.simulate('click');

  //this assertion fails as the state has not updated and is still 'state is empty'
  expect(mockLoad).toBeCalledWith('state is nonempty');
});

如上所述,以下是有效的,但如果有解决方法,我宁愿不包括期望:

setTimeout(() => {
    expect(mockLoad).toBeCalledWith('state is nonempty');
    done();
  }, 50);

我还可以改变我如何模拟函数来删除可行的承诺:

const mockLoad = jest.fn((text) => {
  return {
    then: function (callback) {
      return callback({
        data : [1, 2]
      })
    }
  }
});

但我想回复一个承诺。

2 个答案:

答案 0 :(得分:2)

出于性能原因,请对批次setState进行批处理,因此此时

expect(mockLoad).toBeCalledWith('state is nonempty');

条件

if (this.state.results.length === 0) {

很可能仍为true,因为data尚未 添加到state

你最好的选择是

  • 在第一个和第二个click event之间使用forceUpdate

  • 或者将测试分成两个单独的,同时提取测试之外的常用逻辑。即使it子句也会更具描述性,例如:第一次测试时为it('calls service correctly when state is empty'),第二次测试时类似。

我赞成第二种方法。

  

setState()并不总是立即更新组件。它可以批量推迟更新或推迟更新。

了解更多here

答案 1 :(得分:0)

将Sinon与Sinon Stub Promise一起使用我能够让它工作。存根承诺库删除了承诺的异步方面,这意味着state及时更新渲染:

const sinon = require('sinon');
const sinonStubPromise = require('sinon-stub-promise');
sinonStubPromise(sinon);

it('Calls service differently based on results', () => {

    const mockLoad = jest.fn((text) => {
        return sinon.stub().returnsPromise().resolves({data: [1, 2]})();
    });

    const serviceStub = {load: mockLoad};

    let component = mount(<App service={serviceStub}/>);
    let button = component.find("#submit");

    button.simulate('click');

    expect(mockLoad).toBeCalledWith('state is empty');

    button.simulate('click');

    expect(mockLoad).toBeCalledWith('state is nonempty');
});

请参阅:

http://sinonjs.org/

https://github.com/substantial/sinon-stub-promise