2016-08-03 120 views
11

测试componentDidMount中的异步调用是否设置React组件的状态的最佳方式是什么?对于上下文,我用于测试的库是Mocha,Chai,EnzymeSinon如何在componentDidMount中测试异步调用,以设置React组件的状态

下面是一个例子代码:

/* 
* assume a record looks like this: 
* { id: number, name: string, utility: number } 
*/ 

// asyncComponent.js 
class AsyncComponent extends React.Component { 
    constructor(props) { 
     super(props); 

     this.state = { 
      records: [] 
     }; 
    } 

    componentDidMount() { 
     // assume that I'm using a library like `superagent` to make ajax calls that returns Promises 

     request.get('/some/url/that/returns/my/data').then((data) => { 
      this.setState({ 
       records: data.records 
      }); 
     }); 
    } 

    render() { 
     return (
      <div className="async_component"> 
       { this._renderList() } 
      </div> 
     ); 
    } 

    _renderList() { 
     return this.state.records.map((record) => { 
      return (
       <div className="record"> 
        <p>{ record.name }</p> 
        <p>{ record.utility }</p> 
       </div> 
      ); 
     }); 
    } 
} 


// asyncComponentTests.js 
describe("Async Component Tests",() => { 
    it("should render correctly after setState in componentDidMount executes",() => { 
     // I'm thinking of using a library like `nock` to mock the http request 

     nock("http://some.url.com") 
      .get("/some/url/that/returns/my/data") 
      .reply(200, { 
       data: [ 
        { id: 1, name: "willson", utility: 88 }, 
        { id: 2, name: "jeffrey", utility: 102 } 
       ] 
      }); 

     const wrapper = mount(<AsyncComponent />); 

     // NOW WHAT? This is where I'm stuck. 
    }); 
}); 
+0

难道你不只是断言你的状态更新正确吗?我并不熟悉使用Enzyme并且不使用'shallow()'api,但对于浅渲染组件,您可以假定状态更新是同步的。 –

+0

我的问题更侧重于这个异步部分 - 如果我最初在渲染之后声明状态,那么'records'将是空数组。相反,我希望在'componentDidMount'中的承诺将状态设置为非空数组之后进行断言。 – wmock

+2

实际上,最好的做法是将该功能移出组件,以便可以单独进行测试,并且可以对其进行嘲讽以测试组件。但是你总是可以使用setTimeout。你可以控制诺克,所以你可以肯定答案会花多长时间。 – aray12

回答

0

忽略的,理智的,建议重新考虑结构,去了解这个方法可能是:

  • 模拟请求(FX与sinon),使其返回承诺一些记录
  • 使用酶的mount函数
  • 断言该州还没有你的记录
  • 让您休息功能使用done回调
  • 等一等(FX与setImmediate),这将确保你的诺言再次解决
  • 断言安装的部件上,这时候检查状态设置
  • 打电话给你做回调,以通知测试已完成

因此,简而言之:

// asyncComponentTests.js 
describe("Async Component Tests",() => { 
    it("should render correctly after setState in componentDidMount executes", (done) => { 
     nock("http://some.url.com") 
      .get("/some/url/that/returns/my/data") 
      .reply(200, { 
       data: [ 
        { id: 1, name: "willson", utility: 88 }, 
        { id: 2, name: "jeffrey", utility: 102 } 
       ] 
      }); 

     const wrapper = mount(<AsyncComponent />); 

     // make sure state isn't there yet 
     expect(wrapper.state).to.deep.equal({}); 

     // wait one tick for the promise to resolve 
     setImmediate(() => { 
      expect(wrapper.state).do.deep.equal({ .. the expected state }); 
      done(); 
     }); 
    }); 
}); 

注:

我不知道箭扣线索,所以在这里我假设你的代码是正确的

0

IMO,这其实是出现更加复杂,因为承诺和componentDidMount的一个共同问题: 你试图测试只在另一个函数范围内定义的函数。即你应该分开你的功能并单独测试它们。

组件

class AsyncComponent extends React.Component { 
    constructor(props) { 
     super(props); 

     this.state = { 
      records: [] 
     }; 
    } 

    componentDidMount() { 
     request.get('/some/url/that/returns/my/data') 
      .then(this._populateState); 
    } 

    render() { 
     return (
      <div className="async_component"> 
       { this._renderList() } 
      </div> 
     ); 
    } 

    _populateState(data) { 
     this.setState({ 
      records: data.records 
     }); 
    } 

    _renderList() { 
     return this.state.records.map((record) => { 
      return (
       <div className="record"> 
        <p>{ record.name }</p> 
        <p>{ record.utility }</p> 
       </div> 
      ); 
     }); 
    } 
} 

单元测试

// asyncComponentTests.js 
describe("Async Component Tests",() => { 
    describe("componentDidMount()",() => { 
     it("should GET the user data on componentDidMount",() => { 
      const data = { 
       records: [ 
        { id: 1, name: "willson", utility: 88 }, 
        { id: 2, name: "jeffrey", utility: 102 } 
       ] 
      }; 
      const requestStub = sinon.stub(request, 'get').resolves(data); 
      sinon.spy(AsyncComponent.prototype, "_populateState"); 
      mount(<AsyncComponent />); 

      assert(requestStub.calledOnce); 
      assert(AsyncComponent.prototype._populateState.calledWith(data)); 
     }); 
    }); 

    describe("_populateState()",() => { 
     it("should populate the state with user data returned from the GET",() => { 
      const data = [ 
       { id: 1, name: "willson", utility: 88 }, 
       { id: 2, name: "jeffrey", utility: 102 } 
      ]; 

      const wrapper = shallow(<AsyncComponent />); 
      wrapper._populateState(data); 

      expect(wrapper.state).to.deep.equal(data); 
     }); 
    }); 
}); 

注意:我写单从文件的单元测试,所以使用shallowmountassert,和expect可能不是最佳实践。

0

所以,你真正想要测试的是,基于一些模拟数据,它“应该正确渲染......”

正如有些人指出的,实现这一目标的一个好方法是将数据提取逻辑放入一个单独的容器中,并拥有一个只知道如何渲染props的“哑”呈现组件。

这里是如何做到这一点: (我不得不稍作修改为打字稿与Tslint,但你的想法)

export interface Props { 
    // tslint:disable-next-line:no-any 
    records: Array<any>; 
} 

// "dumb" Component that converts props into presentation 
class MyComponent extends React.Component<Props> { 
    // tslint:disable-next-line:no-any 
    constructor(props: Props) { 
     super(props); 
    } 

    render() { 
     return (
      <div className="async_component"> 
       {this._renderList()} 
      </div> 
     ); 
    } 

    _renderList() { 
     // tslint:disable-next-line:no-any 
     return this.props.records.map((record: any) => { 
      return (
       <div className="record" key={record.name}> 
        <p>{record.name}</p> 
        <p>{record.utility}</p> 
       </div> 
      ); 
     }); 
    } 
} 

// Container class with the async data loading 
class MyAsyncContainer extends React.Component<{}, Props> { 

    constructor(props: Props) { 
     super(props); 

     this.state = { 
      records: [] 
     }; 
    } 

    componentDidMount() { 

     fetch('/some/url/that/returns/my/data') 
     .then((response) => response.json()) 
     .then((data) => { 
      this.setState({ 
       records: data.records 
      }); 
     }); 
    } 

    // render the "dumb" component and set its props 
    render() { 
     return (<MyComponent records={this.state.records}/>); 
    } 
} 

现在你可以给你的模拟测试MyComponent渲染数据为道具。

相关问题