繁体   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