Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to receive a take with runSaga / redux-saga

I created a recordSaga function, its target is to record what actions have been dispatched during the saga.

export const recordSaga = async (saga, initialAction, state) => {
  const dispatched = [];

  const done = await runSaga(
    {
      dispatch: action => dispatched.push(action),
      getState: () => state,
    },
    saga,
    initialAction,
  ).done;

  return {
    dispatched,
    done,
  };
};

so let's say my saga is this one

export function* mySaga() {
  const needToSave = yield select(needToSaveDocument);
  if (needToSave) {
    yield put(saveDocument());
    yield take(SAVE_DOCUMENT_SUCCESS);
  }
  yield put(doSomethingElse())
}

I want to write two tests, which I expect to be the following

describe('mySaga', async () => {
  it('test 1: no need to save', async () => {    
    const state = { needToSave: false }
    const { dispatched } = await recordSaga(mySaga, {}, state);
    expect(dispatched).toEqual([
      doSomethingElse()
    ])
  })
  it('test 2: need to save', async () => {
    const state = { needToSave: true }
    const { dispatched } = await recordSaga(mySaga, {}, state);
    expect(dispatched).toEqual([
      saveDocument(),
      doSomethingElse()
    ])
  })
})

However, for the test 2 where there is a take in between, and of course jest (or its girlfriend jasmine) is yelling at me: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

I know it is because runSaga is waiting for the take(SAVE_DOCUMENT_SUCCESS), but how can I mock that up ?

like image 324
arnaudambro Avatar asked Nov 06 '22 17:11

arnaudambro


1 Answers

The answer stdChannel().put({type, payload})

Why ?

Using stdChannel you can dispatch after the first run.

How ?

  • import stdChannel;
  • add to the first param in runSaga;
  • call stdChannel().put(SAVE_DOCUMENT_SUCCESS);

Example

what worked for me

I left the first test as it is the expected final result, but the solution comes on the last 2.


  import { runSaga, stdchannel } from 'redux-saga'
  let dispatchedActions = [];
  let channel;
  let fakeStore;

  beforeEach(() => {
    channel = stdChannel(); // you have to declare the channel to have access to it later
    fakeStore = {
      channel, // add it to the store in runSaga
      getState: () => "initial",
      dispatch: (action) => dispatchedActions.push(action),
    };
  });

  afterEach(() => {
    global.fetch.mockClear();
  });

 it("executes getData correctly", async () => {
    await runSaga(fakeStore, getData, getAsyncData("test")).toPromise();
    expect(global.fetch.mock.calls.length).toEqual(1);
    expect(dispatchedActions[0]).toEqual(setData(set_value));
  });

  it("triggers takeLatest and call getData(), but unfortunately doesn't resolve promise", async () => {
    await runSaga(fakeStore, rootSaga)// .toPromise() cannot be used here, as will throw Timeout error
    channel.put(getAsyncData("test")); // if remove this line, the next 2 expects() will fail
    expect(global.fetch.mock.calls.length).toEqual(1);
    // expect(dispatchedActions[1]).toEqual(setData(set_value)); // will fail here, but pass on the next it()
  });

  it("takes the promised data from test above", () => {
    expect(dispatchedActions[1]).toEqual(setData(set_value));
  });

this answer (about true code, not tests) helped me

like image 108
Tales Avatar answered Nov 17 '22 05:11

Tales