2016-08-03 5 views
11

Was ist der beste Weg zu Test, dass ein asynchroner Aufruf innerhalb componentDidMount den Status für eine React-Komponente setzt? Für den Kontext sind die Bibliotheken, die ich zum Testen verwende, Mocha, Chai, Enzyme und Sinon.Testen von Async-Aufrufen in componentDidMount, die den Status von React Component setzen

Hier ist ein Beispielcode:

/* 
* 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

Würden Sie nicht einfach behaupten, dass Ihr Status korrekt aktualisiert wurde? Ich bin nicht so vertraut mit der Verwendung von Enzyme und nicht mit der 'shallow()' API, aber mit seicht gerenderten Komponenten können Sie davon ausgehen, dass die Zustandsaktualisierung synchron ist. –

+0

Meine Frage konzentriert sich mehr auf den asynchronen Teil davon - wenn ich den Zustand nach dem Rendern behaupten würde, wäre 'records' das leere Array. Stattdessen hoffe ich, die Assertion nach dem Versprechen in 'componentDidMount' zu setzen, um den Status auf ein nicht leeres Array zu setzen. – wmock

+2

In der Praxis ist es die beste Vorgehensweise, diese Funktionalität aus der Komponente zu entfernen, damit sie separat getestet werden kann, und Sie können die Komponente testen, um sie zu testen. Aber Sie könnten immer setTimeout verwenden. Sie haben die Kontrolle über Nock, so dass Sie ziemlich sicher sein können, wie lange die Antwort dauern wird. – aray12

Antwort

0

Ignorieren der, gesund, Beratung wieder zu denken über die Struktur, die eine Art und Weise, um dies zu können sein:

  • Mock die Anfrage (fx mit sinon), machen es ein Versprechen für einige Datensätze zurück
  • Verwendung des Enzyms mount Funktion
  • Assert, dass der Staat nicht Ihre Unterlagen haben noch
  • Haben Sie Ihre Ruhe Funktion Gebrauch done Rückruf
  • Warten Sie ein bisschen (fx mit setImmediate), dies wird Ihr Versprechen sicherstellen
  • Assert auf dem montierten Bauteil wieder aufgelöst wird, überprüft dieses Mal, dass der Staat gesetzt wurde
  • Rufen Sie Ihren getan Rückruf zu benachrichtigen, dass der Test

kurz So, abgeschlossen hat:

// 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(); 
     }); 
    }); 
}); 
Hinweis

:

Ich habe keine Ahnung über Nock, hier so gehe ich davon aus Ihrem Code

0

IMO korrekt ist, das ist eigentlich ein gemeinsames Problem, das wegen Versprechungen komplizierter erscheint und componentDidMount: Sie versuchen, um Funktionen zu testen, die nur im Rahmen einer anderen Funktion definiert sind. Sie sollten Ihre Funktionen aufteilen und einzeln testen.

Komponente

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> 
      ); 
     }); 
    } 
} 

Einheit Test

// 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); 
     }); 
    }); 
}); 

Hinweis: I die Unit-Tests aus Dokumentation allein geschrieben haben, so dass die Verwendung von shallow, mount, assert, und expect sind möglicherweise keine Best Practices.

0

Also, was Sie wirklich versuchen zu testen ist, dass basierend auf einigen Scheindaten "sollte richtig rendern ...".

Wie einige Leute darauf hingewiesen haben, ist ein guter Weg, dies zu erreichen, indem die Logik zum Abrufen von Daten in einen separaten Container gestellt wird und eine "dumme" Darstellungskomponente hat, die nur props rendern kann.

Hier ist, wie das tun: (ich es ein bisschen für Typoskript mit Tslint zu ändern hatte, aber Sie bekommen die Idee)

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}/>); 
    } 
} 

Jetzt können Sie testen MyComponent Rendering von Ihrem Mock geben Daten als Requisiten.

Verwandte Themen