I am facing an isse while testing my saga function:
function * onSaveDATA() {
try {
yield put( showStatusMessage({ messageContent: 'Saving Your Data' }));
const body = yield select( state => state.appData.userDetails );
yield call( postDATA, { body });
yield put( hideStatusMessage());
yield put({ type: ActionTypes.SAVE_DATA_OK });
} catch ( e ) {
yield put({ type: ActionTypes.CRITICAL_ERROR_OCCURED, payload: e });
}
}
export function * save_on_change( ) {
yield takeEvery( ActionTypes.SAVE_DATA_REQ, onSaveDATA );
}
Here is a unit test which I have written to test this function, but it is failing the test. I am not sure what is wrong.
import { runSaga } from 'redux-saga';
import { postDATA } from './../../../../services/my_service';
// import { openModalMessage } from './../../../../actions';
import { saveDATA } from './';
jest.mock( './../../../../services/my_service' );
jest.mock( './../../../../actions' );
describe( 'Saga: Save Data', () => {
test( 'saveDATA OK', async () => {
postDATA.mockReset();
postDATA.mockReturnValue( {s:'Somevalue'} );
const dispatchedActions = [];
await runSaga({
dispatch: action => dispatchedActions.push( action ),
getState: () => ({
appState: {},
appData: { userDetails: {name:'mock-name'}},
}),
}, save_on_change );
expect( postDATA ).toHaveBeenCalled();
});
});
When I run this it fails. I am not sure what am I missing here. Is it because the saveDATA
function is using factory function takeEvery. Do I need to explicilty trigger the action SAVE_DATA_REQ?
Here is unit test solution for "redux-saga": "^1.1.3"
:
index.ts
:
import { put, select, call, takeEvery } from 'redux-saga/effects';
import { postDATA } from './service';
export const ActionTypes = {
SAVE_DATA_OK: 'SAVE_DATA_OK',
CRITICAL_ERROR_OCCURED: 'CRITICAL_ERROR_OCCURED',
SAVE_DATA_REQ: 'SAVE_DATA_REQ',
};
const showStatusMessage = (payload) => ({ type: 'SHOW_STATUS_MESSAGE', payload });
const hideStatusMessage = () => ({ type: 'HIDE_STATUS_MESSAGE' });
export function* onSaveDATA() {
try {
yield put(showStatusMessage({ messageContent: 'Saving Your Data' }));
const body = yield select((state) => state.appData.userDetails);
yield call(postDATA, { body });
yield put(hideStatusMessage());
yield put({ type: ActionTypes.SAVE_DATA_OK });
} catch (e) {
yield put({ type: ActionTypes.CRITICAL_ERROR_OCCURED, payload: e });
}
}
export function* save_on_change() {
yield takeEvery(ActionTypes.SAVE_DATA_REQ, onSaveDATA);
}
service.ts
:
export async function postDATA(data) {
return { s: 'real data' };
}
index.test.ts
:
import { runSaga } from 'redux-saga';
import { onSaveDATA, ActionTypes, save_on_change } from './';
import { postDATA } from './service';
import { mocked } from 'ts-jest/utils';
import { takeEvery } from 'redux-saga/effects';
jest.mock('./service');
describe('62952662', () => {
afterAll(() => {
jest.resetAllMocks();
});
describe('onSaveDATA', () => {
test('should save data', async () => {
mocked(postDATA).mockResolvedValueOnce({ s: 'Somevalue' });
const dispatchedActions: any[] = [];
await runSaga(
{
dispatch: (action) => dispatchedActions.push(action),
getState: () => ({
appState: {},
appData: { userDetails: { name: 'mock-name' } },
}),
},
onSaveDATA,
).toPromise();
expect(postDATA).toBeCalledWith({ body: { name: 'mock-name' } });
expect(dispatchedActions).toEqual([
{ type: 'SHOW_STATUS_MESSAGE', payload: { messageContent: 'Saving Your Data' } },
{ type: 'HIDE_STATUS_MESSAGE' },
{ type: ActionTypes.SAVE_DATA_OK },
]);
});
test('should handle error if postDATA error', async () => {
const mError = new Error('network');
mocked(postDATA).mockRejectedValueOnce(mError);
const dispatchedActions: any[] = [];
await runSaga(
{
dispatch: (action) => dispatchedActions.push(action),
getState: () => ({
appState: {},
appData: { userDetails: { name: 'mock-name' } },
}),
},
onSaveDATA,
).toPromise();
expect(postDATA).toBeCalledWith({ body: { name: 'mock-name' } });
expect(dispatchedActions).toEqual([
{ type: 'SHOW_STATUS_MESSAGE', payload: { messageContent: 'Saving Your Data' } },
{ type: ActionTypes.CRITICAL_ERROR_OCCURED, payload: mError },
]);
});
});
describe('save_on_change', () => {
test('should wait for every SAVE_DATA_REQ action and call onSaveDATA', () => {
const gen = save_on_change();
expect(gen.next().value).toEqual(takeEvery(ActionTypes.SAVE_DATA_REQ, onSaveDATA));
expect(gen.next().done).toBeTruthy();
});
});
});
unit test results with coverage report:
PASS src/stackoverflow/62952662/index.test.ts
62952662
onSaveDATA
✓ should save data (6 ms)
✓ should handle error if postDATA error (2 ms)
save_on_change
✓ should wait for every SAVE_DATA_REQ action and call onSaveDATA (1 ms)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 95 | 100 | 83.33 | 93.75 |
index.ts | 100 | 100 | 100 | 100 |
service.ts | 50 | 100 | 0 | 50 | 2
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 2.928 s, estimated 3 s