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

Я новичок в тестировании Jest и moxios. Просто пытаюсь написать свой первый тест асинхронного действия. Тест умирает с этой ошибкой:

Expected value to equal:
  [{"payload": {"checked": true, "followingInfoId": "1"}, "type": "HANDLE_FAVORITE_SUCCESS"}]
Received:
  [{"payload": [TypeError: Cannot read property 'getItem' of undefined], "type": "ERROR"}]

Кто-нибудь может сказать мне, где проблема. Я полагаю, что ответ moxios не идет на «тогда»?

import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import moxios from 'moxios';
import * as actions from './index';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const store = mockStore();

describe('followings actions', () => {
  beforeEach(() => {
    moxios.install();
    store.clearActions();
  });

  afterEach(() => {
    moxios.uninstall();
  });

  it('dispatches the HANDLE_FAVORITE_SUCCESS action', () => {
    moxios.wait(() => {
      const request = moxios.requests.mostRecent();
      request.respondWith({
        status: 200,
        payload: {
          followingInfoId: '1',
          checked: true
        }
      });
    });

    const expectedActions = [
      {
        'type': 'HANDLE_FAVORITE_SUCCESS',
        payload: {
          followingInfoId: '1',
          checked: true
        }
      }
    ];

    return store.dispatch(actions.handleFavorite()).then(() => {
      expect(store.getActions()).toEqual(expectedActions);
    });
  });
});

Вот создатель действия:

export const handleFavorite = data => {
  return dispatch => {
    return followApi.handleFavorite(data).then(payload => {
      dispatch({ type: 'HANDLE_FAVORITE_SUCCESS', payload });
    }, err => {    
      dispatch({ type: 'ERROR', payload: err })
    });
  }
};

Вот следующийApi.handleFavorite:

handleFavorite: (data) => {
    return new Promise ((resolve, reject) => {
      httpServise.patch(`${host}:${port}/followings/handle-favorite`, data).then(
        res => {
          if (res.data.payload) {
            resolve(res.data.payload);
          } else reject({status: 401});
        }, err => reject(err)
      );
    });
  },

И часть http-сервиса при необходимости:

patch: (url, params) => {
  return new Promise((resolve, reject) => {
      axios(url, {
          method: 'PATCH',
          headers: getHeaders(),
          data: params
      }).then(res => {
          resolve(res);
      }, err => {
          reject(err);
      });
  });
}

person Andrew Carbon    schedule 26.08.2018    source источник
comment
Пожалуйста, не могли бы вы опубликовать код создателя действия handleFavorite?   -  person A Jar of Clay    schedule 27.08.2018
comment
@ Баночка с глиной, конечно.   -  person Andrew Carbon    schedule 27.08.2018
comment
Похоже, создатель действия ожидает какие-то данные, но вы не передаете их в функцию в тесте. Чтобы убедиться, что проблема именно в этом, нужен код followApi.handleFavorite!   -  person A Jar of Clay    schedule 27.08.2018
comment
@ Баночка с глиной, готово. Вы хотите передать некоторые данные в actions.handleFavorite()?   -  person Andrew Carbon    schedule 27.08.2018
comment
Отсутствие передачи каких-либо данных не обязательно является проблемой. Судя по коду, ошибка происходит где-то в followApi.handleFavorite, но трудно сказать где. Возможно, попробуйте записать каждый шаг, который происходит внутри этой функции и подфункций, чтобы увидеть, поможет ли это.   -  person A Jar of Clay    schedule 27.08.2018
comment
@ Банка глины, после 100500 журналов консоли я понял, в чем проблема. Я использовал настоящий HTTP-сервис своего проекта, и мне пришлось создать макет, чтобы тест работал правильно. Теперь я должен использовать этот mockAxios.patch() вместо httpService.patch(), но что, если я хочу работать с обоими? Первый, когда действие тестируется, а второй, когда есть реальный запрос? Что ты предлагаешь?   -  person Andrew Carbon    schedule 28.08.2018
comment
Нет, это не лучший способ сделать это. Ваш текущий код выглядит так, как будто он должен работать, поэтому, не попробовав его, я не могу вам больше помочь. Я лично использую встроенные функции шутливого макета вместо таких инструментов, как moxios, так что вы можете попробовать это, например, сделать что-то вроде того, что я предлагаю в другом ответе здесь: stackoverflow.com/a/51654713/7470360   -  person A Jar of Clay    schedule 28.08.2018


Ответы (1)


Если вы хотите протестировать создателей действий, вам следует имитировать метод followApi.handleFavorite, а не axios.

Вот решение для тестирования создателей действий, использующих только jestjs и typescript. Вы можете смоделировать модуль вручную самостоятельно.

Структура папки:

.
├── actionCreators.spec.ts
├── actionCreators.ts
├── followApi.ts
└── httpServise.ts

actionCreators.ts:

import followApi from './followApi';

export const handleFavorite = data => {
  return dispatch => {
    return followApi.handleFavorite(data).then(
      payload => {
        dispatch({ type: 'HANDLE_FAVORITE_SUCCESS', payload });
      },
      err => {
        dispatch({ type: 'ERROR', payload: err });
      }
    );
  };
};

followApi.ts:

import { httpServise } from './httpServise';

const host = 'http://github.com/mrdulin';
const port = 3000;

const followApi = {
  handleFavorite: data => {
    return new Promise((resolve, reject) => {
      httpServise.patch(`${host}:${port}/followings/handle-favorite`, data).then(
        (res: any) => {
          if (res.data.payload) {
            resolve(res.data.payload);
          } else {
            reject({ status: 401 });
          }
        },
        err => reject(err)
      );
    });
  }
};

export default followApi;

httpService.ts:

import axios from 'axios';

function getHeaders() {
  return {};
}

export const httpServise = {
  patch: (url, params) => {
    return new Promise((resolve, reject) => {
      axios(url, {
        method: 'PATCH',
        headers: getHeaders(),
        data: params
      }).then(
        res => {
          resolve(res);
        },
        err => {
          reject(err);
        }
      );
    });
  }
};

actionCreators.spec.ts:

import configureMockStore from 'redux-mock-store';
import thunk, { ThunkDispatch } from 'redux-thunk';
import { AnyAction } from 'redux';
import * as actions from './actionCreators';
import followApi from './followApi';

jest.mock('./followApi.ts', () => {
  return {
    handleFavorite: jest.fn()
  };
});

type State = any;
const middlewares = [thunk];
const mockStore = configureMockStore<State, ThunkDispatch<State, undefined, AnyAction>>(middlewares);
const store = mockStore();

describe('followings actions', () => {
  beforeEach(() => {
    store.clearActions();
    jest.resetAllMocks();
  });
  it('dispatches the HANDLE_FAVORITE_SUCCESS action', () => {
    expect.assertions(2);
    const mockedHandleFavoritePayload = {
      followingInfoId: '1',
      checked: true
    };
    (followApi.handleFavorite as jest.MockedFunction<typeof followApi.handleFavorite>).mockResolvedValueOnce(
      mockedHandleFavoritePayload
    );
    const data = 'jest';
    const expectedActions = [
      {
        type: 'HANDLE_FAVORITE_SUCCESS',
        payload: {
          followingInfoId: '1',
          checked: true
        }
      }
    ];
    return store.dispatch(actions.handleFavorite(data)).then(() => {
      expect(store.getActions()).toEqual(expectedActions);
      expect(followApi.handleFavorite).toBeCalledWith(data);
    });
  });

  it('dispatches the ERROR action', () => {
    const mockedhHndleFavoriteError = new Error('network error');
    (followApi.handleFavorite as jest.MockedFunction<typeof followApi.handleFavorite>).mockRejectedValueOnce(
      mockedhHndleFavoriteError
    );
    const data = 'jest';
    const expectedActions = [
      {
        type: 'ERROR',
        payload: mockedhHndleFavoriteError
      }
    ];
    return store.dispatch(actions.handleFavorite(data)).then(() => {
      expect(store.getActions()).toEqual(expectedActions);
      expect(followApi.handleFavorite).toBeCalledWith(data);
    });
  });
});

Результат модульного теста с отчетом о 100% покрытии:

PASS  src/stackoverflow/52025257/actionCreators.spec.ts (5.95s)
  followings actions
    ✓ dispatches the HANDLE_FAVORITE_SUCCESS action (5ms)
    ✓ dispatches the ERROR action (2ms)

-------------------|----------|----------|----------|----------|-------------------|
File               |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files          |      100 |      100 |      100 |      100 |                   |
 actionCreators.ts |      100 |      100 |      100 |      100 |                   |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        6.87s, estimated 7s

Исходный код: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/52025257

person slideshowp2    schedule 28.09.2019