Как unit test этот редукс?

Итак, у меня есть создатель Action Redux, который использует redux thunk middleware:

accountDetailsActions.js:

export function updateProduct(product) {
  return (dispatch, getState) => {
    const { accountDetails } = getState();

    dispatch({
      type: types.UPDATE_PRODUCT,
      stateOfResidence: accountDetails.stateOfResidence,
      product,
    });
  };
}

Как это проверить? Я использую пакет chai для тестирования. Я нашел некоторые ресурсы в Интернете, но я не уверен, как это сделать. Вот мой тест:

accountDetailsReducer.test.js:

describe('types.UPDATE_PRODUCT', () => {
    it('should update product when passed a product object', () => {
        //arrange
        const initialState = {
            product: {}
        };
        const product = {
            id: 1,
            accountTypeId: 1,
            officeRangeId: 1,
            additionalInfo: "",
            enabled: true
        };
        const action = actions.updateProduct(product);
        const store = mockStore({courses: []}, action);
        store.dispatch(action);
        //this is as far as I've gotten - how can I populate my newState variable in order to test the `product` field after running the thunk?
        //act
        const newState = accountDetailsReducer(initialState, action);
        //assert
        expect(newState.product).to.be.an('object');
        expect(newState.product).to.equal(product);
    });
});

My thunk не выполняет никаких асинхронных действий. Любые советы?

Ответ 1

Как юнит-тест Redux Thunks

Весь смысл создателя Thunk Action состоит в том, чтобы отправлять асинхронные действия в будущем. При использовании redux-thunk хорошим подходом является моделирование асинхронного потока начала и конца, приводящего к успеху или ошибке с тремя действиями.

Хотя в этом примере для тестирования используются Mocha и Chai, вы с такой же легкостью можете использовать любую библиотеку утверждений или среду тестирования.

Моделирование асинхронного процесса с несколькими действиями, управляемыми нашим основным создателем Thunk Action

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

  • Когда начинается асинхронная операция
  • Когда асинхронная операция заканчивается
  • Была ли асинхронная операция успешной или неудачной

Итак, пора моделировать наши редукционные действия на основе этих этапов жизненного цикла операции. Помните, что то же самое относится ко всем асинхронным операциям, поэтому это обычно применяется к http-запросам для получения данных из API.

Мы можем написать наши действия так.

accountDetailsActions.js:

export function updateProductStarted (product) {
  return {
    type: 'UPDATE_PRODUCT_STARTED',
    product,
    stateOfResidence
  }
}

export function updateProductSuccessful (product, stateOfResidence, timeTaken) {
  return {
    type: 'PRODUCT_UPDATE_SUCCESSFUL',
    product,
    stateOfResidence
    timeTaken
  }
}

export function updateProductFailure (product, err) {
  return {
    product,
    stateOfResidence,
    err
  }
}

// our thunk action creator which dispatches the actions above asynchronously
export function updateProduct(product) {
  return dispatch => {
    const { accountDetails } = getState()
    const stateOfResidence = accountDetails.stateOfResidence

    // dispatch action as the async process has begun
    dispatch(updateProductStarted(product, stateOfResidence))

    return updateUser()
        .then(timeTaken => {
           dispatch(updateProductSuccessful(product, stateOfResidence, timeTaken)) 
        // Yay! dispatch action because it worked
      }
    })
    .catch(error => {
       // if our updateUser function ever rejected - currently never does -
       // oh no! dispatch action because of error
       dispatch(updateProductFailure(product, error))

    })
  }
}

Обратите внимание на занятые действия внизу. Это наш создатель Thunk Action. Поскольку он возвращает функцию, это специальное действие, которое перехватывается промежуточным программным обеспечением redux-thunk. Создатель этого действия может отправить других создателей действий в будущем. Довольно умный.

Теперь мы написали действия для моделирования асинхронного процесса, который является обновлением пользователя. Допустим, этот процесс является вызовом функции, которая возвращает обещание, как это было бы наиболее распространенным сегодня подходом к работе с асинхронными процессами.

Определите логику для фактической асинхронной операции, которую мы моделируем с помощью действий с избыточностью

Для этого примера мы просто создадим универсальную функцию, которая возвращает обещание. Замените это фактической функцией, которая обновляет пользователей или выполняет асинхронную логику. Убедитесь, что функция возвращает обещание.

Мы будем использовать функцию, определенную ниже, чтобы создать работающий автономный пример. Чтобы получить рабочий пример, просто добавьте эту функцию в ваш файл действий, чтобы она была в поле действия вашего создателя Thunk Action.

 // This is only an example to create asynchronism and record time taken
 function updateUser(){
      return new Promise( // Returns a promise will be fulfilled after a random interval
          function(resolve, reject) {
              window.setTimeout(
                  function() {
                      // We fulfill the promise with the time taken to fulfill
                      resolve(thisPromiseCount);
                  }, Math.random() * 2000 + 1000);
          }
      )
})

Наш тестовый файл

import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import chai from 'chai' // You can use any testing library
let expect = chai.expect;

import { updateProduct } from './accountDetailsActions.js'

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

describe('Test thunk action creator', () => {
  it('expected actions should be dispatched on successful request', () => {
    const store = mockStore({})
    const expectedActions = [ 
        'updateProductStarted', 
        'updateProductSuccessful'
    ]

    return store.dispatch(fetchSomething())
      .then(() => {
        const actualActions = store.getActions().map(action => action.type)
        expect(actualActions).to.eql(expectedActions)
     })

  })

  it('expected actions should be dispatched on failed request', () => {
    const store = mockStore({})
    const expectedActions = [ 
        'updateProductStarted', 
        'updateProductFailure'
    ]

    return store.dispatch(fetchSomething())
      .then(() => {
        const actualActions = store.getActions().map(action => action.type)
        expect(actualActions).to.eql(expectedActions)
     })

  })
})

Ответ 2

Взгляните на Рецепт: Написание тестов из официальной документации. Кроме того, что вы тестируете, создатель действий или редуктор?

Пример теста Action Creator

describe('types.UPDATE_PRODUCT', () => {
    it('should update product when passed a product object', () => {    
        const store = mockStore({courses: []});
        const expectedActions = [
            / * your expected actions */
        ];

        return store.dispatch(actions.updateProduct(product))
            .then(() => {
                expect(store.getActions()).to.eql(expectedActions);
            });
    });
});

Пример испытания редуктора

Ваш редуктор должен быть чистой функцией, чтобы вы могли тестировать его изолированно вне среды магазина.

const yourReducer = require('../reducers/your-reducer');

describe('reducer test', () => {
    it('should do things', () => {
        const initialState = {
            product: {}
        };

        const action = {
            type: types.UPDATE_PRODUCT,
            stateOfResidence: // whatever values you want to test with,
            product: {
                id: 1,
                accountTypeId: 1,
                officeRangeId: 1,
                additionalInfo: "",
                enabled: true
            }
        }

        const nextState = yourReducer(initialState, action);

        expect(nextState).to.be.eql({ /* ... */ });
    });
});

Ответ 3

Для тестирования асинхронного действия рекомендуется использовать модуль redux-thunk-tester.

Источник.

Ответ 4

export const someAsyncAction = (param) => (dispatch, getState) => {
    const { mock } = getState();
    dispatch({
        type: 'SOME_TYPE',
        mock: mock + param,
    })
}

it('should test someAsyncAction', () => {
    const param = ' something';
    const dispatch = jest.fn().mockImplementation();
    const getState = () => ({
        mock: 'mock value',
    });

    const expectedAction = {
        type: 'SOME_TYPE',
        mock: 'mock value something'
    };

    const callback = someAsyncAction(param);
    expect(typeof callback).toBe('function');

    callback.call(this, dispatch, getState);
    expect(dispatch.mock.calls[0]).toEqual([expectedAction])
});