Search code examples
reactjstestingjestjsredux-saga

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

  • 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