Reputation: 9787
If I have a saga with this form:
function * sagaWorker() {
yield put(START_ACTION)
yield take(WAIT_FOR_ACTION)
yield delay(100)
yield put(END_ACTION)
}
I can successfully test it using runSaga
like this:
step('saga passes the tests', async () => {
const channel = stdChannel()
const dispatched = []
const options = {
dispatch: action => dispatched.push(action),
getState: () => {},
channel
}
const task = runSaga(options, sagaWorker)
channel.put(WAIT_FOR_ACTION)
await task.toPromise()
expect(dispatched).to.deep.eql([START_ACTION, END_ACTION])
})
However, if I move the delay in front of the take:
function * sagaWorker() {
yield put(START_ACTION)
yield delay(100)
yield take(WAIT_FOR_ACTION)
yield put(END_ACTION)
}
Now the saga doesn't run to completion and times out - it gets to the take
but the action never arrives in the channel.
Is it possible to test it using this form? I suspect I can make it work by call
ing the delay
s rather than yield
ing them directly but I'd like to know how to make it work without doing that (if it's possible).
Upvotes: 6
Views: 1722
Reputation: 120450
Using yield call(() => myPromiseyDelay(500))
won't save you here. There will still be nothing to notice the "lost" action at the time it's dispatched.
When you post your WAIT_FOR_ACTION
the saga is is in a yielded state on the yield delay
. There is no queue for actions here, so by the time you get to yield take(WAIT_FOR_ACTION)
, the WAIT_FOR_ACTION
action has long since been dispatched, unnoticed by any of the saga logic you presented above (there was no active take
to grab the action).
Consider setting up an actionChannel
to capture these unlistened-for actions. They'll be queued up in the channel, ready-to-consume, after the delay
has completed.
So, something like:
function * sagaWorker() {
const channel = yield actionChannel(WAIT_FOR_ACTION)
yield put(START_ACTION)
yield delay(100)
yield take(channel)
yield put(END_ACTION)
}
So putting this all together as non-pseudocode:
const {
runSaga,
stdChannel,
effects: {
take,
put,
actionChannel,
delay
}
} = window.ReduxSaga
const WAIT_FOR_ACTION = "WAIT_FOR_ACTION";
const START_ACTION = "START_ACTION";
const END_ACTION = "END_ACTION";
(async() => {
const channel = stdChannel();
const dispatched = [];
const options = {
dispatch: action => dispatched.push(action),
getState: () => {},
channel
};
const task = runSaga(options, sagaWorker);
channel.put({
type: WAIT_FOR_ACTION
});
await task.toPromise();
console.log(dispatched);
})();
function* sagaWorker() {
const channel = yield actionChannel(WAIT_FOR_ACTION);
yield put({
type: START_ACTION
});
yield delay(100);
yield take(channel);
yield put({
type: END_ACTION
});
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/redux-saga.umd.min.js"></script>
Upvotes: 10