'How to change state during saga test

I have created polling saga which calls backend API until the response from the API is marked as done or until saga encounters an error.

export function* pollingSaga() {
  while (true) {
    try {
      yield call(/*call to BE API*/);
      const response = yield select(getResponse)
      if (response.result !== 'done') {
        yield call(delay, POLLING_INTERVAL_MS)
      } else {
        yield put(actions.cancel())
        return
      }
    } catch (error) {
      yield put(actions.cancel())
      return
    }
  }
}

I want to create test that checks if the delay is called properly and then if the response from API is done to check if the saga dispatches proper cancel action

it('should poll backend until maximum amount of rows is fetched', () => {
  const gen = pollingQueryResultsSaga()
  /*Here I would like to mock the call to the API so it returns response 'pending'*/
  gen.next() // call
  gen.next() // select 'pending' value
  expect(gen.next().value).toEqual(call(delay, 1000);
  /*Here I would like again mock API response but right now with 'done' value so my saga will return from `while` loop and dispatch cancel action */
  gen.next() // new iteration, call

  expect(gen.next().value).toEqual(put(actions.cancel());
  gen.next()
  expect(gen.next().done).toBe(true);
})


Solution 1:[1]

You can send a value to the generator by calling gen.next(value) to change the response value. See Generator.prototype.next().

The value will be assigned as a result of a yield expression

E.g.

index.ts:

import { call, put, select } from 'redux-saga/effects';

const api = () => '';
const getResponse = (state) => state.response;
const POLLING_INTERVAL_MS = 1000;
export const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export const actions = {
  cancel: () => ({ type: 'CANCEL' }),
};

export function* pollingSaga() {
  while (true) {
    try {
      yield call(api);
      const response = yield select(getResponse);
      console.log('response: ', response);
      if (response.result !== 'done') {
        yield call(delay, POLLING_INTERVAL_MS);
      } else {
        yield put(actions.cancel());
        return;
      }
    } catch (error) {
      yield put(actions.cancel());
      return;
    }
  }
}

index.test.ts:

import { call, put } from 'redux-saga/effects';
import { actions, delay, pollingSaga } from '.';

describe('72260266', () => {
  it('should poll backend until maximum amount of rows is fetched', () => {
    const gen = pollingSaga();
    gen.next();
    gen.next();
    expect(gen.next({ result: 'pending' }).value).toEqual(call(delay, 1000));
    gen.next();
    gen.next();
    expect(gen.next({ result: 'done' }).value).toEqual(put(actions.cancel()));
    expect(gen.next().done).toBe(true);
  });
});

Test result:

 PASS   redux-saga-examples  packages/redux-saga-examples/src/stackoverflow/72260266/index.test.ts (5.039 s)
  72260266
    ? should poll backend until maximum amount of rows is fetched (51 ms)

  console.log
    response:  { result: 'pending' }

      at packages/redux-saga-examples/src/stackoverflow/72260266/index.ts:16:15

  console.log
    response:  { result: 'done' }

      at packages/redux-saga-examples/src/stackoverflow/72260266/index.ts:16:15

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |      72 |       80 |   33.33 |   88.89 |                   
 index.ts |      72 |       80 |   33.33 |   88.89 | 24-25             
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        7.19 s

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1