krishnanspace
krishnanspace

Reputation: 423

Redux saga testing using runSaga not updating the state

So I am trying to test out a redux saga using the runSaga function. This saga gives to call to an API and stores its response in the state by calling an action. After storing it, I am retrieving the stored data using a selector and performing some actions. I am able to mock the API response and also call the success action once the API has returned.

When in the next line when I try to call the selector to fetch the data from the store, it seems to be getting the initial state from the store and not the updated one after the API success.

Here is my code:

// loadSaga.js. This is the saga that I'm testing
export function* loadSaga(request) {
  const endpoint = yield select(ListEndpointSelector);
  const listResponse = yield call(request, endpoint, 'GET', {}, throwIfNot404);
  console.log(listResponse); // Returns the correct mocked API response
  yield put(loadListSuccess(listResponse));
  const { list as newList } = yield select(listSelector);
  console.log(newList); // Returns empty array
  // ... do something with newList but newList is empty
};
// loadSaga.test.js
import { runSaga } from 'redux-saga';
import * as reduxSagaEffects from 'redux-saga/effects';

const mockState = {
    user: {
      list: [],
    },
  },
};
describe('loadSaga', () => {
  it('returns the correct list of users', async () => {
    const dispatchedActions = [];
    const mockUsers = [
    {
     id: 1,
     name: 'abc',
    },
    {
     id: 2,
     name: 'xyz',
    },
    ]

    const mockfn = jest.fn().mockImplementationOnce(() => mockUsers);

    // eslint-disable-next-line
    const m = jest.mock('redux-saga/effects', () => ({
      ...reduxSagaEffects,
      call: mockfn,
    }));
    const { loadSaga } = require('./loadSaga.js');
    runSaga({
      dispatch: (action) => dispatchedActions.push(action),
      getState: () => (mockState),
    }, loadSaga);

    console.log(dispatchedActions);
});

When I console listResponse, I get the correct mockedResponse from the API which I set in the mockFn. But when I console the newList, it returns [] which seems to be the list set in the mockState.

The console.log for dispatched actions shows the correct actions being dispatched, even the loadListSuccess with the mocked API response being passed to it.

Since the yield select(listSelector) is not returning the correct output, I am not able to test the further test cases. What should I change so that I am able to retrieve the current updated state in the selector?

Upvotes: 0

Views: 1553

Answers (1)

Lin Du
Lin Du

Reputation: 102237

loadSaga saga does not connect to the redux store. select(selector, ...args) just creates an effect object. When the redux store uses redux-saga as middleware, redux-saga can only get the getState method of redux and pass it to the effect created by select.

You can use getState() option of runSaga to create a mocked store. You have to maintain the correctness of the state data yourself to ensure that the subsequent logic that depends on the list is executed correctly.

Besides, you don't need to mock the call effect creator of redux-saga, since loadSaga accepts a request handler, you can create a mock for it and pass it to the third parameter of runSaga.

E.g.

loadSaga.ts:

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

const listSelector = (state) => {
  console.log('state: ', state);
  return state.user;
};

export function loadListSuccess(payload) {
  return { type: 'LOAD_LIST_SUCCESS', payload };
}

export function* loadSaga(request) {
  const listResponse = yield call(request);
  console.log('listResponse: ', listResponse);
  yield put(loadListSuccess(listResponse));
  const { list } = yield select(listSelector);
  console.log('list: ', list);
}

loadSaga.test.ts:

import { runSaga } from 'redux-saga';
import { loadListSuccess, loadSaga } from './loadSaga';

describe('68632358', () => {
  test('should pass', async () => {
    const dispatchedActions: any[] = [];
    const mockUsers = [
      { id: 1, name: 'abc' },
      { id: 2, name: 'xyz' },
    ];
    const mockState = {
      user: {
        list: mockUsers,
      },
    };
    const mRequest = jest.fn().mockResolvedValue(mockUsers);
    await runSaga(
      {
        dispatch: (action) => dispatchedActions.push(action),
        getState: () => mockState,
      },
      loadSaga as any,
      mRequest,
    ).toPromise();
    expect(mRequest).toBeCalled();
    expect(dispatchedActions).toEqual([loadListSuccess(mockUsers)]);
  });
});

test result:

 PASS  src/stackoverflow/68632358/loadSaga.test.ts
  68632358
    ✓ should pass (21 ms)

  console.log
    listResponse:  [ { id: 1, name: 'abc' }, { id: 2, name: 'xyz' } ]

      at src/stackoverflow/68632358/loadSaga.ts:14:11

  console.log
    state:  { user: { list: [ [Object], [Object] ] } }

      at listSelector (src/stackoverflow/68632358/loadSaga.ts:4:11)

  console.log
    list:  [ { id: 1, name: 'abc' }, { id: 2, name: 'xyz' } ]

      at src/stackoverflow/68632358/loadSaga.ts:17:11

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.641 s, estimated 2 s

package version: "redux-saga": "^1.1.3"

Upvotes: 1

Related Questions