簡體   English   中英

如何使用鏈式承諾測試react componentDidMount中的異步提取?

[英]How do I test async fetch in react componentDidMount with chained promises?

我一直在嘗試了解如何測試在componentDidMount期間運行異步提取的已安裝componentDidMount

問題是我可以讓它等待初始提取觸發,但不要等待從promise中解析所有鏈。

這是一個例子:

import React from "react";

class App extends React.Component {
  state = {
    groceries: [],
    errorStatus: ""
  };

  componentDidMount() {
    console.log("calling fetch");

    fetch("/api/v1/groceries")
      .then(this.checkStatus)
      .then(this.parseJSON)
      .then(this.setStateFromData)
      .catch(this.setError);
  }

  checkStatus = results => {
    if (results.status >= 400) {
      console.log("bad status");

      throw new Error("Bad Status");
    }

    return results;
  };

  setError = () => {
    console.log("error thrown");

    return this.setState({ errorStatus: "Error fetching groceries" });
  };

  parseJSON = results => {
    console.log("parse json");

    return results.json();
  };

  setStateFromData = data => {
    console.log("setting state");

    return this.setState({ groceries: data.groceries });
  };

  render() {
    const { groceries } = this.state;

    return (
      <div id="app">
        {groceries.map(grocery => {
          return <div key={grocery.id}>{grocery.item}</div>;
        })}
      </div>
    );
  }
}

export default App;

測試:

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import React from 'react';
import { mount } from 'enzyme'
import App from './App';

Enzyme.configure({ adapter: new Adapter() });

const mockResponse = (status, statusText, response) => {
  return new window.Response(response, {
    status: status,
    statusText: statusText,
    headers: {
      'Content-type': 'application/json'
    }
  });
};

describe('App', () => {
  describe('componentDidMount', () => {
    it('sets the state componentDidMount', async () => {
      console.log('starting test for 200')

      global.fetch = jest.fn().mockImplementation(() => Promise.resolve(
        mockResponse(
          200,
          null,
          JSON.stringify({
            groceries: [
              { item: 'nuts', id: 10 }, { item: 'greens', id: 3 }
            ]
          })
        )
      ));

      const renderedComponent = await mount(<App />)
      await renderedComponent.update()

      console.log('finished test for 200')
      expect(renderedComponent.state('groceries').length).toEqual(2)
    })

    it('sets the state componentDidMount on error', async () => {
      console.log('starting test for 500')

      window.fetch = jest.fn().mockImplementation(() => Promise.resolve(
        mockResponse(
          400,
          'Test Error',
          JSON.stringify({ status: 400, statusText: 'Test Error!' })
        )
      ))

      const renderedComponent = await mount(<App />)
      await renderedComponent.update()

      console.log('finished test for 500')
      expect(renderedComponent.state('errorStatus')).toEqual('Error fetching groceries')
    })
  })
})

當它運行時,我收到此控制台日志記錄的順序(請注意,測試完成,然后記錄該狀態已設置):

console.log src/App.test.js:22
  starting test for 200

console.log src/App.js:10
  calling fetch

console.log src/App.js:36
  parse json

console.log src/App.test.js:39
  finished test for 200

console.log src/App.js:42
  setting state

我已經創建了一個代碼的示例沙箱:

編輯3xkq4my426

這在我的應用程序中被抽象得更多,因此更改代碼本身要困難得多(例如,我想在更高的組件上進行測試,該組件具有redux存儲,並且這個較低的組件調用fetch,並最終通過設置存儲一個thunk)。

這是如何測試的?

更新方法實際上沒有返回承諾,這就是await無法正常工作的原因。 要修復單元測試,您可以將fetch調用移動到另一個方法,並在測試中使用該函數,以便等待正常工作。

 componentDidMount() {
    console.log("calling fetch");

    this.fetchCall();
  }

  fetchCall() {
    return fetch("/api/v1/groceries")
      .then(this.checkStatus)
      .then(this.parseJSON)
      .then(this.setStateFromData)
      .catch(this.setError);
  }

使用instance()來訪問fetchCall方法。

const renderedComponent = mount(<App />);
await renderedComponent.instance().fetchCall();

我修改了代碼框中的上述更改: https ://codesandbox.io/s/k38m6y89o7

我不知道為什么await renderedComponent.update() .update await renderedComponent.update()在這里沒有幫助( .update不返回Promise但它仍然意味着下面的所有內容都是分開的微任務)。

但將東西包裝到setTimeout(..., 0)對我setTimeout(..., 0) 所以微任務和macrotask之間的區別實際上是以某種方式發生的。

   it("sets the state componentDidMount on error", done => {
      console.log("starting test for 500");

      window.fetch = jest
        .fn()
        .mockImplementation(() =>
          Promise.resolve(
            mockResponse(
              400,
              "Test Error",
              JSON.stringify({ status: 400, statusText: "Test Error!" })
            )
          )
        );

      const renderedComponent = mount(<App />);
      setTimeout(() => {
        renderedComponent.update();

        console.log("finished test for 500");
        expect(renderedComponent.state("errorStatus")).toEqual(
          "Error fetching groceries"
        );
        done();
      }, 0);
    });
  });

這種方法的唯一缺點是:當expect()失敗時,它不會向Jest輸出顯示失敗的消息。 Jest只是抱怨測試還沒有在5000毫秒完成。 在同一時間有效的錯誤消息,如Expected value to equal: ...轉到控制台。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM