如何对 Redux Thunk 进行单元测试
thunk 动作创建者的全部目的是在将来分派异步动作。使用 redux-thunk 时,一个好的方法是对开始和结束的异步流程进行建模,通过三个操作导致成功或错误。
尽管此示例使用 Mocha 和 Chai 进行测试,但您可以轻松地使用任何断言库或测试框架。
使用由我们的主要 thunk 动作创建者管理的多个动作对异步流程进行建模
在本示例中,我们假设您想要执行更新产品的异步操作,并且想要了解三个关键事项。
- 当异步操作开始时
- 当异步操作完成时
- 异步操作是否成功或失败
好的,现在是时候根据操作生命周期的这些阶段来建模我们的 redux 操作了。请记住,这同样适用于所有异步操作,因此这通常适用于从 api 获取数据的 http 请求。
我们可以这样写我们的动作。
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 动作创建者。由于它返回一个函数,因此它是一个被 redux-thunk 中间件拦截的特殊操作。那个 thunk 动作创建者可以在未来的某个时刻派遣其他动作创建者。相当聪明。
现在我们已经编写了对异步过程(即用户更新)进行建模的操作。假设这个过程是一个返回承诺的函数调用,这将是当今处理异步过程的最常见方法。
定义我们使用 redux 操作建模的实际异步操作的逻辑
对于这个例子,我们将只创建一个返回承诺的通用函数。将其替换为更新用户或执行异步逻辑的实际函数。确保该函数返回一个承诺。
我们将使用下面定义的函数来创建一个工作的独立示例。要获得一个有效的示例,只需将此函数放入您的操作文件中,这样它就位于您的 thunk 操作创建者的范围内。
// 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)
})
})
})