Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux runSaga (Unit testing) error throws undefined on local variable. How do I mock a local variable using Jest or Sinon?

Running runSaga on my redux saga generator function my window variable is thrown as undefined but my test files are passing is there any way to mock the window variable?

Below is my redux saga generator function

import api from 'my-api';
    
const getSuburb = () => window.userCookies.selectedSuburb;

function* saga(payload) {
 const action = yield take('REQUEST_LIBRARY');
 const selectedSuburb = yield call(getSuburb);
 const getStateLibraries = yield call(api.getLibraries, selectedSuburb, action.userId);
 yield put(loadLibrary(getStateLibraries)
}

On running the above code I receive list of libraries with respect to the suburb, I have another state holds the suburb information where I can use select to retrieve it. The code works fine

Unit test cases to test redux saga using RunSaga

const recordSaga = async function (sagaHandler, initalAction) {
  const dispatchedActions = [];
  const fakeStore = {
    getState: () => (initialState),
    dispatch: action => dispatchedActions.push(action),
  };
  await runSaga(
    fakeStore,
    sagaHandler,
    initalAction,
  ).done;
  return dispatchedActions;
};

describe('Run Saga', () => {
 it('should dispatch action libraries', async() => {
  const dispatched = await recordSaga(saga, { user_id:2 });
  expect(dispatched).toContainEqual(loadLibrarySuccess(someProfile));
 }

While running the above I am getting on selectedSuburb as undefined because window.userCookies.totalSuburbs is not defined, is there any better way to mock the getSuburb function?

like image 884
kishorekumaru Avatar asked Oct 31 '19 00:10

kishorekumaru


1 Answers

I recommend to look at how Redux-Saga docs suggest sagas testing. In unit test only logic of function saga should be tested and not calls to any third party function. And yield keyword greatly helps with this.

Essentially we test that generator function (function saga in this example) will return correct objects on each call. So unit test for saga function will look like (not tested, just sample)

describe('Run Saga', () => {
  it('should dispatch action libraries', () => {
    const gen = saga()  // As saga is generator function it will return generator object
    expect(gen.next().value).toStrictEqual(take('REQUEST_LIBRARY'))
    expect(gen.next({ userId: 'user1' } /* here can be action returned by take('REQUEST_LIBRARY') in saga */).value).toStrictEqual(call(getSuburb))
    expect(gen.next({ suburb: 'central suburb' } /* here can be selectedSuburb returned by call(getSuburb) in saga */).value).toStrictEqual(call(api.getLibraries, { suburb: 'central suburb' }, 'user1'))
    // The values of selectedSuburb and action.userId are from calls to gen.next()
    expect(gen.next({ libraries: ['lib 1', 'lib 2'] } }).value).toStrictEqual(put(loadLibrary({ libraries: ['lib 1', 'lib 2'] }))
    expect(gen.next().done).toBe(true); // Saga is finished
  }
})

So unit test only checks saga itself and don't call any functions that saga calls when executed. So you don't need to mock anything at all when using sagas. This confirm to idea of unit testing to test only single unit of code (function) in isolation.

As you may noticed, value returned from yield should be passed to next() function but on next line. This is how generator works.

like image 183
Fyodor Avatar answered Oct 13 '22 07:10

Fyodor