2016-08-03 116 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渲染數據爲道具。

相關問題