import { runSaga } from 'redux-saga';
import { call, put, select } from 'redux-saga/effects';
import { expectSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
function* fetchUserSaga(action) {
try {
const user = yield call(api.fetchUser, action.payload.id);
yield put({ type: 'FETCH_USER_SUCCESS', payload: user });
} catch (error) {
yield put({ type: 'FETCH_USER_ERROR', error });
}
}
describe('fetchUserSaga', () => {
const userId = 1;
const user = { id: 1, name: 'John' };
test('should fetch user successfully', () => {
return expectSaga(fetchUserSaga, { payload: { id: userId } })
.provide([
[matchers.call.fn(api.fetchUser), user]
])
.put({ type: 'FETCH_USER_SUCCESS', payload: user })
.run();
});
test('should handle errors', () => {
const error = new Error('User not found');
return expectSaga(fetchUserSaga, { payload: { id: userId } })
.provide([
[matchers.call.fn(api.fetchUser), throwError(error)]
])
.put({ type: 'FETCH_USER_ERROR', error })
.run();
});
});
function* complexSaga() {
const user = yield select(state => state.user);
yield call(api.trackUserAction, user.id);
const data = yield call(api.fetchData);
yield put({ type: 'DATA_LOADED', payload: data });
}
test('complex saga flow', () => {
const fakeUser = { id: 1 };
const fakeData = { items: [] };
return expectSaga(complexSaga)
.withState({
user: fakeUser
})
.provide([
[matchers.call.fn(api.trackUserAction), null],
[matchers.call.fn(api.fetchData), fakeData]
])
.call(api.trackUserAction, fakeUser.id)
.put({ type: 'DATA_LOADED', payload: fakeData })
.run();
});
function* parentSaga() {
yield fork(childSaga1);
yield fork(childSaga2);
}
test('forked sagas', () => {
return expectSaga(parentSaga)
.provide([
])
.fork(childSaga1)
.fork(childSaga2)
.run();
});
function* raceSaga() {
const { response, timeout } = yield race({
response: call(api.fetchData),
timeout: delay(1000)
});
}
test('race condition', () => {
const response = { data: 'success' };
return expectSaga(raceSaga)
.provide([
[matchers.race({
response: matchers.call.fn(api.fetchData),
timeout: matchers.call.fn(delay)
}), { response }]
])
.run();
});
function* watchUserSaga() {
while (true) {
const action = yield take('USER_REQUEST');
yield call(handleUserRequest, action);
}
}
test('watch saga', () => {
const action = { type: 'USER_REQUEST', payload: 1 };
return expectSaga(watchUserSaga)
.take('USER_REQUEST')
.dispatch(action)
.call(handleUserRequest, action)
.silentRun();
});
test('dynamic providers', () => {
return expectSaga(mySaga)
.provide({
call: (effect, next) => {
if (effect.fn === api.fetchUser) {
return { id: 1, name: 'John' };
}
return next();
}
})
.run();
});
test('saga cancellation', () => {
return expectSaga(mySaga)
.provide([
[matchers.fork.fn(backgroundTask), true]
])
.fork(backgroundTask)
.cancel.like({ pattern: backgroundTask })
.run();
});
test('partial state matching', () => {
return expectSaga(mySaga)
.withState({
user: { id: 1 },
})
.run();
});
const createSagaTestHelper = (initialState = {}) => {
const dispatched = [];
return {
dispatch: (action) => dispatched.push(action),
getState: () => initialState,
getDispatched: () => dispatched
};
};
test('manual saga testing', async () => {
const dispatched = [];
const fakeStore = {
dispatch: (action) => dispatched.push(action),
getState: () => ({ user: { id: 1 } })
};
await runSaga(
fakeStore,
mySaga,
{ type: 'START_SAGA' }
).toPromise();
expect(dispatched).toEqual([
]);
});