Модульное тестирование ваших вызовов api может быть до некоторой степени обременительным, для простоты здесь я использую приложение create-react-app и Enzyme and Jest (без redux или redux-thunk).

В нашем приложении есть единственный компонент реакции <Hello/>, и мы собираемся сделать вызов API, чтобы получить информацию о пользователе в его методе жизненного цикла componentDidMount.

А отображать имя пользователя в абзаце после успешного вызова API.

Пока вызов api не будет разрешен, мы будем показывать span для загрузки.

Итак, наш компонент Hello имеет

import React from "react";
export default class Hello extends React.Component {
    constructor(props) {
       super(props);
       this.state = {
         user: null
       }
    }
    async componentDidMount() {
     try {
         const res = await fetch("http://localhost:3000/user");
         if (res.status >= 400)
            throw new Error("something went wrong")
         const user = await res.json();
         this.setState({ user });
      } catch (err) {
        console.error(err);
      }
   }

   render() {
       return (
         <div>
           <h2>Welcome to hello</h2>
            
            {this.state.user ?
            <p className="user">user:{this.state.user.userName}</p>
             :
             <span className="spinner"></span>}
         </div>
      );
    }
}

в приведенном выше коде я использую fetch, чтобы выполнить ajax-вызов конечной точки api, и я пометил componentDidMount ключевым словом async, чтобы мы могли await, пока наш вызов api не будет разрешен. После успешного вызова api мы конвертируем ответ json в объект пользователя javascript и вызываем setState с данными пользователя.

Давайте напишем для этого тест

Для компонента Hello мы напишем два теста

Компонент Hello должен отображать загрузку span до успешного вызова API

Должен отобразить paragraphtag с именем пользователя после успешного вызова API и скрыть загрузку spans.

import React from "react";
import { shallow } from "enzyme";
import Hello from "./hello";
beforeAll(() => {
  global.fetch = jest.fn();
  //window.fetch = jest.fn(); if running browser environment
});
let wrapper;
beforeEach(() => {
   wrapper = shallow(<Hello />, { disableLifecycleMethods: true });
});
afterEach(() => {
   wrapper.unmount();
});
it("must render a loading span before api call success", () => {
    expect(wrapper.find("span.spinner").exists()).toBeTruthy();
});
it("must show the p.user and hide the loading span after api call success", 
(done) => {
// here we are spying on componentDidMount to know that it has been called
const spyDidMount = jest.spyOn(Hello.prototype,"componentDidMount");
fetch.mockImplementation(() => {
   return Promise.resolve({
     status: 200,
     json: () => {
     return Promise.resolve({
        userName: "manas",
        userId: 1
      });
    }
  });
});
const didMount = wrapper.instance().componentDidMount();
// expecting componentDidMount have been called
expect(spyDidMount).toHaveBeenCalled();
didMount.then(() => {
   // updating the wrapper
   wrapper.update();
   expect(wrapper.find("p.user").text()).toContain("manas");
   expect(wrapper.find("spans.loading").length).toBe(0);
   spyDidMount.mockRestore();
   fetch.mockClear();
   done();
});
});

давайте разберем код тестирования на значимые части

Перед всеми тестами я издеваюсь над вызовом api с jest.fn(). Это хорошая практика - имитировать вызов API в модульном тесте, поскольку мы проводим модульный тест, а не интеграционный тест.

beforeAll(() => {
  global.fetch = jest.fn();
  //window.fetch = jest.fn(); if running browser environment
});

Я создаю свойство fetch в глобальном объекте с помощью global.fetch = jest.fn() и устанавливаю его на функцию шутливого макета, чтобы сделать fetch доступным в среде nodejs. Если вы запускаете свой код в codeanbox или jsfiddle, используйте вместо этого window.fetch = jest.fn().

let wrapper;
beforeEach(() => {
   wrapper = shallow(<Hello />, { disableLifecycleMethods: true });
});
afterEach(() => {
   wrapper.unmount();
});

Перед каждым тестом я создаю неглубокую оболочку для Hello компонента, используя метод поверхностного рендеринга Enzyme.

Здесь следует отметить, что shallow функция вызывается со вторым параметром {disableLifecycleMethods:true}.

Это предотвращает неглубокий рендеринг для вызова событий жизненного цикла реакции, таких как componentDidMount, после рендеринга. В результате componentDidMount не будет вызываться автоматически после неглубокой визуализации, и мы можем проверить наличие загрузки span перед успешным вызовом api.

it("must render a loading span before api call success", () => {
    expect(wrapper.find("span.loading").length).toBe(1);
});

приступим ко второму тесту.

Должен отображать тег paragraph с именем пользователя после успешного вызова API и скрывать загрузку spans

const spyDidMount = jest.spyOn(Hello.prototype,"componentDidMount");

В приведенном выше коде мы шпионим за componentDidMount, чтобы проверить, был ли вызван componentDidMount.

fetch.mockImplementation(() => {
   return Promise.resolve({
     status: 200,
     json: () => {
     return Promise.resolve({
        userName: "manas",
        userId: 1
      });
    }
  });
});

В приведенном выше коде мы создаем фиктивную реализацию вызова fetch для нашего api. Это разрешается в пользовательский объект {userName:"manas",userId:1}.

const didMount = wrapper.instance().componentDidMount();

Теперь я вручную вызываю метод componentDidMount объекта wrapper instance, который возвращает обещание, поскольку наша componentDidMount является async функцией.

didMount.then(() => {
   // updating the wrapper
   wrapper.update();
   expect(wrapper.find("p.user").text()).toContain("manas");
   expect(wrapper.find("spans.loading").length).toBe(0);
   spyDidMount.mockRestore();
   fetch.mockClear();
   done();
});

Поскольку wrapper.instance().componentDidMount() возвращает обещание, мы можем использовать его then() метод для написания наших тестов, перед этим давайте обновим оболочку с помощью wrapper.update(), поскольку успешный вызов api должен был вызвать setState({user}) и это должно было обновить представление Hello компонента.

expect(wrapper.find("p.user").text()).toContain("manas");
expect(wrapper.find("spans.loading").length).toBe(0);

Ожидается, что оболочка теперь должна иметь абзац с текстом manas внутри него. И ожидаем, что span с классом загрузки больше не доступен в обновленном представлении.

spyDidMount.mockRestore();
fetch.mockClear();

Восстановление шпиона и очистка мока.

И последнее, но не менее важное: не забудьте вызвать done(), который сообщает шутку, которую мы закончили с асинхронным тестом.