2017-05-20 1 views
1

sagen, dass ich diese einfache Reagieren Komponente haben:Wie Unit Test Promise catch() -Methode Verhalten mit async/erwarten in Jest?

class Greeting extends React.Component { 
    constructor() { 
     fetch("https://api.domain.com/getName") 
      .then((response) => { 
       return response.text(); 
      }) 
      .then((name) => { 
       this.setState({ 
        name: name 
       }); 
      }) 
      .catch(() => { 
       this.setState({ 
        name: "<unknown>" 
       }); 
      }); 
    } 

    render() { 
     return <h1>Hello, {this.state.name}</h1>; 
    } 
} 

unter die Antworten gegeben und etwas mehr Forschung zu diesem Thema habe ich mit dieser Endlösung zu testen, die resolve() Fall kommen:

test.only("greeting name is 'John Doe'", async() => { 
    const fetchPromise = Promise.resolve({ 
     text:() => Promise.resolve("John Doe") 
    }); 

    global.fetch =() => fetchPromise; 

    const app = await shallow(<Application />); 

    expect(app.state("name")).toEqual("John Doe"); 
}); 

Das funktioniert gut. Mein Problem testet jetzt den catch() Fall. Die folgende funktionierte nicht, wie ich es erwartet funktionieren:

test.only("greeting name is 'John Doe'", async() => { 
    const fetchPromise = Promise.reject(undefined); 

    global.fetch =() => fetchPromise; 

    const app = await shallow(<Application />); 

    expect(app.state("name")).toEqual("<unknown>"); 
}); 

Die Behauptung fehlschlägt, name ist leer:

expect(received).toEqual(expected) 

Expected value to equal: 
    "<unknown>" 
Received: 
    "" 

    at tests/components/Application.spec.tsx:51:53 
    at process._tickCallback (internal/process/next_tick.js:103:7) 

Was bin ich?

Antwort

5

Die Linie

const app = await shallow(<Application />); 

nicht korrekt in beiden Tests ist. Dies würde bedeuten, dass shallow ein Versprechen zurückgibt, was nicht der Fall ist. Daher warten Sie nicht wirklich darauf, dass die Versprechungskette in Ihrem Konstruktor aufgelöst wird, wie Sie es wünschen. Zuerst bewegen, um die Abrufanforderung an componentDidMount, wo die React docs auslösenden Netzwerkanforderungen empfehlen, etwa so:

import React from 'react' 

class Greeting extends React.Component { 
    constructor() { 
    super() 
    this.state = { 
     name: '', 
    } 
    } 

    componentDidMount() { 
    return fetch('https://api.domain.com/getName') 
     .then((response) => { 
     return response.text() 
     }) 
     .then((name) => { 
     this.setState({ 
      name, 
     }) 
     }) 
     .catch(() => { 
     this.setState({ 
      name: '<unknown>', 
     }) 
     }) 
    } 

    render() { 
    return <h1>Hello, {this.state.name}</h1> 
    } 
} 

export default Greeting 

Jetzt haben wir es durch den Aufruf componentDidMount direkt testen können. Da ComponentDidMount die Zusage zurückgibt, wartet await auf die Auflösung der Versprechungskette.

import Greeting from '../greeting' 
import React from 'react' 
import { shallow } from 'enzyme' 

test("greeting name is 'John Doe'", async() => { 
    const fetchPromise = Promise.resolve({ 
    text:() => Promise.resolve('John Doe'), 
    }) 

    global.fetch =() => fetchPromise 

    const app = shallow(<Greeting />) 
    await app.instance().componentDidMount() 

    expect(app.state('name')).toEqual('John Doe') 
}) 

test("greeting name is '<unknown>'", async() => { 
    const fetchPromise = Promise.reject(undefined) 

    global.fetch =() => fetchPromise 

    const app = shallow(<Greeting />) 
    await app.instance().componentDidMount() 

    expect(app.state('name')).toEqual('<unknown>') 
}) 
+0

Sie sind nicht die erste Person, die Ihnen empfohlen hat. Ich verschiebe die Anfrage nach 'componentDidMount'. React-Dokumente empfehlen diesen Lifecycle-Rückruf, um die Netzwerkanforderung zu instanziieren. Aber neben dem Testen und meiner speziellen Frage, ist es eine schlechte Übung, die Netzwerkanforderung im Konstruktor zu machen? Wenn ja warum? –

+0

Habe gerade deine Lösung getestet und es hat gut funktioniert und ich verstehe diese Dinge jetzt besser, also vielen Dank dafür. Ich bin nur neugierig auf meine vorherige Frage ... –

+1

Ein Problem mit der Verwendung des Konstruktors vs des Lebenszyklus ist, dass Sie es möglicherweise Daten unnötig aufrufen nennen. Sie können die Komponente instanziieren, sie dann aber nie bereitstellen, sodass der Netzwerkaufruf nie verwendet wurde. Im Allgemeinen denke ich, dass es wahrscheinlich in jeder Hinsicht funktionieren würde, aber es gibt bestimmte Randfälle, in denen es weniger ideal ist, also wird nur die Verwendung der Lebenszyklen empfohlen. Es kann andere Nachteile geben; nicht sicher. – TLadd

0

Eine andere Möglichkeit, wenn Sie nicht anrufen möchten getan dann den nächsten Versprechen Zustand zu scherzen. Basierend auf dem Ergebnis der Assertion (expect) wird der Testfall fehlschlagen oder bestehen.

beispiel

describe("Greeting",() => { 

    test("greeting name is unknown",() => { 
     global.fetch =() => { 
      return new Promise((resolve, reject) => { 
       process.nextTick(() => reject()); 
      }); 
     }; 
     let app = shallow(<Application />); 
     return global.fetch.catch(() => { 
      console.log(app.state()); 
      expect(app.state('name')).toBe('<unknown>'); 
     }) 
    }); 

}); 
+0

Es hat nicht funktioniert. Zuerst musste ich stattdessen 'global.fetch(). Catch()' aufrufen. Zweitens, nach dieser Änderung ist die Assertion immer noch fehlgeschlagen, wobei 'name' leer ist. Aber es fällt mir auch schwer zu verstehen, warum ich selbst "catch" nennen muss, wenn das automatisch aufgerufen werden soll, wenn ich das Versprechen mit "reject()" ablehne. –

+0

Bitte überprüfen Sie meine Bearbeitung ... –

0

Durch das Aussehen dieses Schnipsels

 .then((response) => { 
      return response.text(); 
     }) 
     .then((name) => { 
      this.setState({ 
       name: name 
      }); 
     }) 

es scheint, dass Text einen String zurückgeben würde, die dann als Name Argument auf dem nächsten 'dann' Block erscheinen würden. Oder gibt es ein Versprechen selbst zurück?

Haben Sie die Funktion spyOn des Spaßes untersucht? Das würde Ihnen helfen, nicht nur den Fetch-Teil zu verspotten, sondern auch, dass die setState-Methode die richtige Anzahl von Malen und mit den erwarteten Werten aufgerufen wurde.

Schließlich denke ich React rät davon ab, Nebenwirkungen innerhalb constructor zu machen. Der Konstruktor sollte vielleicht verwendet werden, um den Anfangszustand und andere Variablen zu setzen. componentWillMount sollte der Weg zu gehen :) sein

+0

'Antwort.text() 'gibt ein Versprechen zurück (https://developer.mozilla.org/en-US/docs/Web/API/Body/text). Aber das Problem in dieser Frage ist das Testen des "Fangens". –