I'm facing some challenges in properly testing my saga. The issue arises because when the saga is running, the reducer is mounted at state: {...initialState}
, while my saga select
effects expect the reducer to be mounted at
state: {authentication: {...initialState}}
Because of this inconsistency, I'm unable to fully test the reducer/saga combination, as the shape of the final state object doesn't match the actual shape of the store.
The saga under test:
export default function* rootAuthenticationSaga() {
while (true) {
refreshToken = yield select((state: ApplicationRootState) => state.authentication.refreshToken);
... more code here
}
One of my tests is as follows:
test('logs the user in if they provide a valid email and password', () => {
const mockRefreshPoller = createMockTask();
const initialState = {
accessToken: '',
refreshToken: '',
userId: '',
}
return expectSaga(rootAuthenticationSaga)
.withReducer(authenticationReducer, initialState)
.provide([
// some mock providers set up here
])
// ...more asserts here
.put(authenticationActions.saveTokens({accessToken: 'VALID_ACCESS_TOKEN', refreshToken: 'VALID_REFRESH_TOKEN'}))
.hasFinalState({
accessToken: 'VALID_ACCESS_TOKEN',
refreshToken: 'VALID_REFRESH_TOKEN',
userId: 'USER_ID',
})
.dispatch(authenticationActions.login.request({email: 'VALID_EMAIL', password: 'VALID_PASSWORD'}))
.run()
});
In the above test, the select()
fails because the correct path (with injection via withReducer) is at state.refreshToken
instead of
state.authentication.refreshToken
When injecting the state via
withState({authentication: {refreshToken, ...}})
, the select
works as expected. However, all the reducer actions happen against the state root, resulting in a final state with the incorrect shape:
{state:
authentication: {
refreshToken: '',
...
},
refreshToken: 'VALID_REFRESH_TOKEN',
...
}