如何在带有链接的Promise的React componentDidMount中测试异步提取?

时间:2018-12-13 00:51:40

标签: javascript reactjs fetch jestjs enzyme

我一直试图了解如何测试在componentDidMount期间运行异步提取的已安装组件。

问题是我可以让它等待初始获取触发,而不是等待从诺言中解决所有链。

这里是一个例子:

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

我创建了一个示例代码沙箱:

Edit 3xkq4my426

这在我的应用程序中被抽象得多,因此更改代码本身要困难得多(例如,我想在具有Redux存储的更高组件上进行测试,而该更低组件则调用fetch并设置最终通过重击存储)。

如何测试?

2 个答案:

答案 0 :(得分:2)

Update方法实际上未返回诺言,这就是await无法正常运行的原因。 要修复单元测试,您可以将fetch调用移至另一种方法,并从测试中使用该函数,以便await正常工作。

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

我已经修改了codeandbox中的上述更改:https://codesandbox.io/s/k38m6y89o7

答案 1 :(得分:0)

我不知道为什么await renderedComponent.update()不能在这里为您提供帮助(.update不会返回Promise,但这仍然意味着下面的所有内容都是单独的微任务)。

但是将内容包装到setTimeout(..., 0)中对我来说很有效。因此,微任务和宏任务之间的区别实际上是以某种方式发生的。

   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输出中显示失败的消息。开玩笑只是抱怨测试没有在5000毫秒内完成。同时,有效错误消息(如Expected value to equal: ...会进入控制台。