Вопрос по redux-thunk, jestjs, reactjs, fetch, redux – https://medium.com/@thejasonfile/fetch-vs-axios-js-for-making-http-requests-2b261cdd3af5

7

ершенно новичок в Jest и, по общему признанию, не являюсь экспертом в тестировании асинхронного кода ...

У меня простойFetch помощник я использую:

export function fetchHelper(url, opts) {
    return fetch(url, options)
        .then((response) => {
            if (response.ok) {
                return Promise.resolve(response);
            }

            const error = new Error(response.statusText || response.status);
            error.response = response;

            return Promise.reject(error);
        });
    }

И реализовать это так:

export function getSomeData() {
    return (dispatch) => {
        return fetchHelper('http://datasource.com/').then((res) => {
            dispatch(setLoading(true));
            return res.json();
        }).then((data) => {
            dispatch(setData(data));
            dispatch(setLoading(false));
        }).catch(() => {
            dispatch(setFail());
            dispatch(setLoading(false));
        });
    };
}

Однако я хочу проверить, что правильные отправки отправляются в правильных обстоятельствах и в правильном порядке.

Раньше это было довольно легко сsinon.spy(), но я не могу понять, как повторить это в Jest. В идеале я бы хотел, чтобы мой тест выглядел примерно так:

expect(spy.args[0][0]).toBe({
  type: SET_LOADING_STATE,
  value: true,
});


expect(spy.args[1][0]).toBe({
  type: SET_DATA,
  value: {...},
});

Заранее спасибо за любую помощь или совет!

Следует избегать отправки нескольких действий подряд с помощью избыточного числа. У вас естьdispatch(setData(data)); dispatch(setLoading(false)); который вызовет 2 изменения магазина и 2 рендера. Если вы объедините это в одно действие и установите для этого состояния загрузки значение false, то в вашем приложении будет только 1 повторный рендеринг. Cory Danielson

Ваш Ответ

4   ответа
5

использующих Redux Thunk или другое промежуточное ПО, лучше всего проверять хранилище Redux для тестов. Вы можете применить промежуточное программное обеспечение к макету магазина, используяredux-mock-store, Чтобы смоделировать HTTP-запрос, вы можете использоватьnock.

Согласно сredux-mock-store документация, вам нужно будет позвонитьstore.getActions() в конце запроса на тестирование асинхронных действий вы можете настроить свой тест как

mockStore(getState?: Object,Function) => store: Function Возвращает экземпляр настроенного фиктивного хранилища. Если вы хотите сбросить свой магазин после каждого теста, вы должны вызвать эту функцию.

store.dispatch(action) => action Отправляет действие через фиктивный магазин. Действие будет сохранено в массиве внутри экземпляра и выполнено.

store.getState() => state: Object Возвращает состояние макета магазина

store.getActions() => actions: Array Возвращает действия макета магазина

store.clearActions() Очищает сохраненные действия

Вы можете написать тестовое действие как

import nock from 'nock';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';

//Configuring a mockStore
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

//Import your actions here
import {setLoading, setData, setFail} from '/path/to/actions';

test('test getSomeData', () => {
    const store = mockStore({});

    nock('http://datasource.com/', {
       reqheaders // you can optionally pass the headers here
    }).reply(200, yourMockResponseHere);

    const expectedActions = [
        setLoading(true),
        setData(yourMockResponseHere),
        setLoading(false)
    ];

    const dispatchedStore = store.dispatch(
        getSomeData()
    );
    return dispatchedStore.then(() => {
        expect(store.getActions()).toEqual(expectedActions);
    });
});

Постскриптум Имейте в виду, что mock-store не обновляется сам по себе, когда запускается смоделированное действие, и если вы зависите от обновленных данных после того, как предыдущее действие будет использовано в следующем действии, то вам нужно написать свой собственный экземпляр этого типа

const getMockStore = (actions) => {
    //action returns the sequence of actions fired and 
    // hence you can return the store values based the action
    if(typeof action[0] === 'undefined') {
         return {
             reducer: {isLoading: true}
         }
    } else {
        // loop over the actions here and implement what you need just like reducer

    }
}

а затем настройтеmockStore любить

 const store = mockStore(getMockStore);

Надеюсь, это поможет. Также проверьтеэто в избыточной документации по тестированию создателей асинхронных действий

1

Если вы издеваетесь над функцией отправки сjest.fn()Вы можете просто получить доступdispatch.mock.calls чтобы получить все звонки на вашу заглушку.

  const dispatch = jest.fn();
  actions.yourAction()(dispatch);

  expect(dispatch.mock.calls.length).toBe(1);

  expect(dispatch.mock.calls[0]).toBe({
    type: SET_DATA,
    value: {...},
  });
Спасибо @Canastro, но это не похоже на работу для меня. Когда я форматирую отправку, как у васactions.yourAction()(dispatch); это выдает ошибку. Я пытался сделать это таким образомdispatch(actions.urnSearch(id)); и когда я вхожуdispatch.mock.calls[0] это просто дает мне[ [Function] ] DanV
0

В своем ответе я используюaxios вместоfetch поскольку у меня нет большого опыта в получении обещаний, это не должно иметь значения для вашего вопроса. Я лично чувствую себя очень комфортно сaxios.
Посмотрите на пример кода, который я предоставляю ниже:

// apiCalls.js
const fetchHelper = (url) => {
  return axios.get(url);
}


import * as apiCalls from './apiCalls'
describe('getSomeData', () => {
  it('should dispatch SET_LOADING_STATE on start of call', async () => {
    spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.resolve());
    const mockDispatch = jest.fn();

    await getSomeData()(mockDispatch);

    expect(mockDispatch).toHaveBeenCalledWith({
      type: SET_LOADING_STATE,
      value: true,
    });
  });

  it('should dispatch SET_DATA action on successful api call', async () => {
    spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.resolve());
    const mockDispatch = jest.fn();

    await getSomeData()(mockDispatch);

    expect(mockDispatch).toHaveBeenCalledWith({
      type: SET_DATA,
      value: { ...},
    });
  });

  it('should dispatch SET_FAIL action on failed api call', async () => {
    spyOn(apiCalls, 'fetchHelper').and.returnValue(Promise.reject());
    const mockDispatch = jest.fn();

    await getSomeData()(mockDispatch);

    expect(mockDispatch).toHaveBeenCalledWith({
      type: SET_FAIL,
    });
  });
});

Здесь я издеваюсь над помощником по извлечению, чтобы вернуть обещание Resolved для проверки успешной части и отклонить обещание протестировать неудавшийся вызов API. Вы также можете передать им аргументы для проверки ответа.
Вы можете реализоватьgetSomeData нравится:

const getSomeData = () => {
  return (dispatch) => {
    dispatch(setLoading(true));
    return fetchHelper('http://datasource.com/')
      .then(response => {
        dispatch(setData(response.data));
        dispatch(setLoading(false));
      })
      .catch(error => {
        dispatch(setFail());
        dispatch(setLoading(false));
      })
  }
}

Я надеюсь, что это решит вашу проблему. Пожалуйста, прокомментируйте, если вам нужны какие-либо разъяснения.
P.S Вы можете увидеть из приведенного выше кода, почему я предпочитаю axios, а не fetch, избавляет вас от многих обещаний, решаемых!
Для дальнейшего чтения на нем вы можете обратиться:https://medium.com/@thejasonfile/fetch-vs-axios-js-for-making-http-requests-2b261cdd3af5

7

статья о тестировании создателей асинхронных действий:

Для создателей асинхронных действий, использующихRedux Thunk или другое промежуточное ПО, лучше всего издеваться над магазином Redux для тестов. Вы можете применить промежуточное программное обеспечение к макету магазина, используяперевождь-макет магазин, Вы также можете использоватьвыборки-макет высмеивать запросы HTTP.

import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from '../../actions/TodoActions'
import * as types from '../../constants/ActionTypes'
import fetchMock from 'fetch-mock'
import expect from 'expect' // You can use any testing library

const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)

describe('async actions', () => {
  afterEach(() => {
    fetchMock.reset()
    fetchMock.restore()
  })

  it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', () => {
    fetchMock
      .getOnce('/todos', { body: { todos: ['do something'] }, headers: { 'content-type': 'application/json' } })


    const expectedActions = [
      { type: types.FETCH_TODOS_REQUEST },
      { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } }
    ]
    const store = mockStore({ todos: [] })

    return store.dispatch(actions.fetchTodos()).then(() => {
      // return of async actions
      expect(store.getActions()).toEqual(expectedActions)
    })
  })
})

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

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

У вас есть ссылка на какую-либо документацию по отправке thunks thunks? Кажется, я не вижу, чтобы их отправили. JP4
Не под рукой. Распространенная проблема, с которой я сталкивался при тестировании thunks, заключается в том, что ожидаемые вызовы выполняются до завершения асинхронного вызова (например, они не возвращают или не ожидают обещания), поэтому дважды проверьте подобные вещи. Если это не так, возможно, лучше открыть свой вопрос с более подробной информацией. Michael Peyper

Похожие вопросы