redux-toolkit: test slice and actions
How to write a simple test for the store
It’s very important to test an application that uses redux, because it is there that our business logic is implemented. It’s signficant to see how the store changes when an action has been dispatched.
For example, we have these actions:
export const fetchUser = createAsyncThunk(
'fetchUser',
async(param: {id}, thunkAPI) => {
const user = await getUserById(param.id);
if(user && user.age >= 18){
thunkAPI.dispatch(action_A());
}
return user;
});export const action_A = createAction(
'action_A', () => {
// do something
})
Imagine to have 2 actions, one of these is an async action that dispatches another different action depending on the result of api.
Let’s see how to deal with this type of test. For this kind of scenario it’s important to test the single asyncThunk action and its flow.
const middlewares = [thunk]
export const mockStore = configureMockStore(middlewares);describe('ACTION TESTS', () => {it('should fetchUser: age > 18', () => {
const user = {id: 1, name: 'John', age: 20}
const expectedActions = [
{
type: fetchUser.pending.type
},
{
type: action_A.type,
payload: {}
},
{
type: fetchUser.fulfilled.type,
payload: user
}
];
const store = mockStore({});// mock API returns
jest.spyOn(importFile,'getUserById').mockImplementation(() =>
Promise.resolve(user));
return store.dispatch(fetchUser({id: 1})).then(() => {
expect(store.getActions().map(action =>
({
type: action.type,
payload: action.payload
})
)).toEqual(expectedActions)
})
})
it('should fetchUser: age < 18', () => {
const user = {id: 2, name: 'Jack', age: 16}
const expectedActions = [
{
type: fetchUser.pending.type
},
{
type: fetchUser.fulfilled.type,
payload: user
}
];
const store = mockStore({});// mock API returns
jest.spyOn(importFile,'getUserById').mockImplementation(() =>
Promise.resolve(user));
return store.dispatch(fetchUser({id: 2})).then(() => {
expect(store.getActions().map(action =>
({
type: action.type,
payload: action.payload
})
)).toEqual(expectedActions)
})
})
})
Through these 2 tests we were able to test the flow of the action: fetchUser, when it has been dispatched.
Another important thing is that we mocked the results of getUserById’s promise returns.P.s. In the getActions, i return an object with only two fields: type and payload.
I kept only the fields that make sense for a test, there are many other fields in an action (meta, arg, etc…)
We just have to test how the store changes. The slice is:
const initialState = { isLoading: false, isError: false}
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchUser.pending, (state, action) => {
return {
...state,
isLoading: true,
isError: false
}
})
builder.addCase(fetchUser.fulfilled, (state, action) => {
return {
...state,
isLoading: false,
isError: false,
[action.payload.id]: {...action.payload}
}
})
builder.addCase(fetchUser.rejected, (state, action) => {
return {
...state,
isLoading: false,
isError: true
}
})
}
})
export default userSlice.reducer// A simple test could be
describe('USER SLICE TESTS', () => {
it('should set loading true while action is pending', () => {
const action = {type: fetchUser.pending};
const initialState = conversionSlice(
{
isLoading: false, isError: false
}, action);
expect(state).toEqual({isError: false, isLoading: true})
})
it('should set user when action is fulfilled', () => {
const action = {
type: fetchUser.fulfilled,
payload:{ id:1, name: 'John', age: 20 }
};
const initialState = conversionSlice(
{
isLoading: false, isError: false
}, action);
expect(state).toEqual({
isError: false,
isLoading: false,
1: { id:1, name: 'John', age: 20 }
})
})
it('should set error true when action is rejected', () => {
const action = {type: fetchUser.rejected};
const initialState = conversionSlice(
{
isLoading: false, isError: false
}, action);
expect(state).toEqual({isError: true, isLoading: false})
})
})
We tested how the state changes when fetchUser has been invoked. It makes sense to test when action is in pending/fulfilled/rejected
As we have seen, testing our store is simple but very important when the flows of the various actions cross each other.
I hope i was helpful.
If you find this post useful, please tap 👏 button below :)