I have a saga that listens for model updates and fires a save to network, debounced by 1 second:
export default function* graphModelUpdate() {
const modelChangeDebounceTime = 1000;
let task;
while (true) {
const action = yield take(IllustrationsActions.GRAPH_MODEL_CHANGED);
if (task) {
yield cancel(task);
}
task = yield fork(function* (payload) {
yield call(delay, modelChangeDebounceTime);
yield put(IllustrationsActions.apiUpdateGraph(payload));
}, action.payload);
}
}
Then I have this test, written in accordance with redux-saga docs and jest docs regarding timers, that tests the saga's effect rather than its implementation:
jest.useFakeTimers();
describe('graphModelUpdate saga', () => {
let dispatched;
let mockState;
let subscribers = [];
const emitAction = (action) => {
subscribers.forEach(cb => cb(action));
};
const options = {
subscribe: callback => {
subscribers.push(callback);
return () => subscribers = subscribers.filter(cb => cb !== callback);
},
dispatch: (action) => dispatched.push(action),
getState: () => mockState
};
beforeEach(() => {
dispatched = [];
mockState = {};
});
it('should listen for IllustrationsActions.GRAPH_MODEL_CHANGED and dispatch IllustrationsActions.apiUpdateGraph, debounced by 1 second', () => {
runSaga(options, graphModelUpdate);
emitAction(IllustrationsActions.graphModelChanged('model one'));
emitAction(IllustrationsActions.graphModelChanged('model two'));
emitAction(IllustrationsActions.graphModelChanged('model three'));
expect(dispatched).toEqual([]);
jest.runTimersToTime(1500);
expect(dispatched).toEqual([
IllustrationsActions.apiUpdateGraph('model three')
]);
});
});
The problem is that since the saga uses a fork
that runs asynchronously, the final expect
is executed before the debounced action is dispatched, no matter how much of the fake time passes (jest.runTimersToTime(1500)
), making the test always fail.
How do I handle such case?