Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing with runSaga with take and delay

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 calling the delays rather than yielding them directly but I'd like to know how to make it work without doing that (if it's possible).

like image 718
Will Jenkins Avatar asked May 15 '19 12:05

Will Jenkins


People also ask

What is done during saga testing?

In your test, you would start a saga, intercept/resolve async effects with effectMiddlewares and assert on things like state updates to test integration between your saga and a store.

How do you do the unit test sagas?

Step by Step approach Our sagas being generator functions always yield effects which are saga factory functions like takeEvery, put, call etc. We can test each yield statement one by one by calling them using next() and asserting the returned values to the expected effect.

How do I use put saga?

put is dispatching a new action with the result from the previous yield. put is non- blocking. In this example, we call put with some action. put is a non-blocking effect creator, so it dispatches an action (could be an action that triggers some other saga), but the saga is not waiting for this action to finish.

What is take in redux saga?

Using Redux Saga to handle multiple async requests Redux Saga then takes care of the invocation and return the result to the generator. The same thing happens with the put method. Instead of dispatching an action inside the generator, put returns an object with instructions for the middleware to dispatch the action.


1 Answers

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>
like image 182
spender Avatar answered Sep 28 '22 03:09

spender