Reputation: 423
So I am trying to test out a redux saga using the runSaga function. This saga gives to call to an API and stores its response in the state by calling an action. After storing it, I am retrieving the stored data using a selector and performing some actions. I am able to mock the API response and also call the success action once the API has returned.
When in the next line when I try to call the selector to fetch the data from the store, it seems to be getting the initial state from the store and not the updated one after the API success.
Here is my code:
// loadSaga.js. This is the saga that I'm testing
export function* loadSaga(request) {
const endpoint = yield select(ListEndpointSelector);
const listResponse = yield call(request, endpoint, 'GET', {}, throwIfNot404);
console.log(listResponse); // Returns the correct mocked API response
yield put(loadListSuccess(listResponse));
const { list as newList } = yield select(listSelector);
console.log(newList); // Returns empty array
// ... do something with newList but newList is empty
};
// loadSaga.test.js
import { runSaga } from 'redux-saga';
import * as reduxSagaEffects from 'redux-saga/effects';
const mockState = {
user: {
list: [],
},
},
};
describe('loadSaga', () => {
it('returns the correct list of users', async () => {
const dispatchedActions = [];
const mockUsers = [
{
id: 1,
name: 'abc',
},
{
id: 2,
name: 'xyz',
},
]
const mockfn = jest.fn().mockImplementationOnce(() => mockUsers);
// eslint-disable-next-line
const m = jest.mock('redux-saga/effects', () => ({
...reduxSagaEffects,
call: mockfn,
}));
const { loadSaga } = require('./loadSaga.js');
runSaga({
dispatch: (action) => dispatchedActions.push(action),
getState: () => (mockState),
}, loadSaga);
console.log(dispatchedActions);
});
When I console listResponse
, I get the correct mockedResponse from the API which I set in the mockFn
. But when I console the newList
, it returns []
which seems to be the list set in the mockState
.
The console.log for dispatched actions shows the correct actions being dispatched, even the loadListSuccess
with the mocked API response being passed to it.
Since the yield select(listSelector)
is not returning the correct output, I am not able to test the further test cases. What should I change so that I am able to retrieve the current updated state in the selector?
Upvotes: 0
Views: 1553
Reputation: 102237
loadSaga
saga does not connect to the redux store. select(selector, ...args)
just creates an effect object. When the redux store uses redux-saga
as middleware, redux-saga
can only get the getState
method of redux and pass it to the effect created by select
.
You can use getState()
option of runSaga
to create a mocked store. You have to maintain the correctness of the state data yourself to ensure that the subsequent logic that depends on the list
is executed correctly.
Besides, you don't need to mock the call
effect creator of redux-saga
, since loadSaga
accepts a request
handler, you can create a mock for it and pass it to the third parameter of runSaga
.
E.g.
loadSaga.ts
:
import { select, call, put } from 'redux-saga/effects';
const listSelector = (state) => {
console.log('state: ', state);
return state.user;
};
export function loadListSuccess(payload) {
return { type: 'LOAD_LIST_SUCCESS', payload };
}
export function* loadSaga(request) {
const listResponse = yield call(request);
console.log('listResponse: ', listResponse);
yield put(loadListSuccess(listResponse));
const { list } = yield select(listSelector);
console.log('list: ', list);
}
loadSaga.test.ts
:
import { runSaga } from 'redux-saga';
import { loadListSuccess, loadSaga } from './loadSaga';
describe('68632358', () => {
test('should pass', async () => {
const dispatchedActions: any[] = [];
const mockUsers = [
{ id: 1, name: 'abc' },
{ id: 2, name: 'xyz' },
];
const mockState = {
user: {
list: mockUsers,
},
};
const mRequest = jest.fn().mockResolvedValue(mockUsers);
await runSaga(
{
dispatch: (action) => dispatchedActions.push(action),
getState: () => mockState,
},
loadSaga as any,
mRequest,
).toPromise();
expect(mRequest).toBeCalled();
expect(dispatchedActions).toEqual([loadListSuccess(mockUsers)]);
});
});
test result:
PASS src/stackoverflow/68632358/loadSaga.test.ts
68632358
✓ should pass (21 ms)
console.log
listResponse: [ { id: 1, name: 'abc' }, { id: 2, name: 'xyz' } ]
at src/stackoverflow/68632358/loadSaga.ts:14:11
console.log
state: { user: { list: [ [Object], [Object] ] } }
at listSelector (src/stackoverflow/68632358/loadSaga.ts:4:11)
console.log
list: [ { id: 1, name: 'abc' }, { id: 2, name: 'xyz' } ]
at src/stackoverflow/68632358/loadSaga.ts:17:11
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.641 s, estimated 2 s
package version: "redux-saga": "^1.1.3"
Upvotes: 1