Как проверить асинхронную выборку в реагирующем компоненте DidMount с цепочками обещаний? - PullRequest
0 голосов
/ 13 декабря 2018

Я пытался понять, как проверить смонтированный компонент, который выполняет асинхронную выборку во время 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

Это намного больше абстрагируется в моих приложениях, поэтому изменение самого кода намного сложнее (например, я хочу протестировать на более высоком компоненте, который имеет избыточное хранилище, и этот более низкий компонент вызывает выборкуи устанавливает магазин в конце концов через thunk).

Как это проверяется?

Ответы [ 2 ]

0 голосов
/ 13 декабря 2018

Понятия не имею, почему await renderedComponent.update() здесь вам не поможет (.update не возвращает Обещание, но это все равно означает, что все, что ниже, приходит как отдельная микрозадача).

Но заверните вещи в 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: ....

0 голосов
/ 13 декабря 2018

Метод обновления фактически не возвращает обещание, поэтому 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();

Я изменил вышеуказанные изменения в кодах и в поле: https://codesandbox.io/s/k38m6y89o7

...