Ответ 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)
})
})
})