Search code examples
reactjsreduxjestjsenzymeredux-saga

How to test redux saga using jest and enzyme


I have a saga function that I am trying to test using jest and enzyme but I am getting an error. This is my first time testing saga file so I do not have an idea why I am getting the error even though expected and receive value are the same saga file is below:

export const getToken = (state) => state.dextaAuth.token;

export function* setPost(action) {
  try {
    const token = yield select(getToken);
    const data = yield call(
      dextaApiService.post,
      "/api/savedAdvices",
      action.payload,
      token
    );
    yield put(
      openNotification({
        messageType: MessageBarType.success,
        message: "Advice saved successfully",
      })
    );
  } catch (err) {
    yield put(
      openNotification({
        messageType: MessageBarType.error,
        message: `Notification: ${err.message}`,
      })
    );
  }
}

export function* savedAdvicesaga() {
  yield takeLatest(POST_SAVED_ADVICE, setPost);
  yield takeLatest(FETCH_SAVED_ADVICE, fetchSavedAsviceAsync);
}

This the test case I have written:

describe("savedAdviceSaga", () => {
  const genObject = savedAdvicesaga();
  describe("setpost", () => {
    it("should check for post ", async () => {
      const apiResponse = jest
        .spyOn(dextaApiService, "post")
        .mockImplementation(() => Promise.resolve());

      const dispatched = [];
      const action: types.SavedAdviceActionType = {
        type: types.POST_SAVED_ADVICE,
        payload: {
          Title: "test",
          Description: "test dec",
        },
      };
      const iterator = setPost(action);
      const effect = iterator.next().value;
      const expected = select(getToken);
      const result = await runSaga(
        {
          dispatch: (action) => dispatched.push(action),
        },
        setPost,
        action
      );
      expect(effect).toContainEqual(expected);
      expect(apiResponse).toHaveBeenCalledTimes(1);
    });
  });
});

This the error I am getting in the console:

expect(received).toContainEqual(expected) // deep equality

Expected value:  {"@@redux-saga/IO": true, "combinator": false, "payload": {"args": [], 
    "selector": [Function anonymous]}, "type": "SELECT"}
Received object: {"@@redux-saga/IO": true, "combinator": false, "payload": {"args": [], "selector": 
     [Function anonymous]}, "type": "SELECT"}

   27 |         dispatch: (action) => dispatched.push(action),
   28 |       }, setPost, action);
 > 29 |       expect(effect).toContainEqual(expected)
      |                      ^
   30 |       expect(apiResponse).toHaveBeenCalledTimes(1);
   31 |
   32 |     })

Solution

  • You are using Testing the full Saga test strategy. Don't need to check each step of the saga.

    run the whole saga and assert that the expected effects have occurred.

    runSaga(options, saga, ...args) accept getState() option, so that you provide a mocked state. The getToken selector will use it.

    E.g.

    saga.ts:

    import { call, put, select, takeLatest } from 'redux-saga/effects';
    import { dextaApiService } from './dextaApiService';
    
    export const MessageBarType = {
      success: 'success',
      error: 'error',
    };
    
    export function openNotification({ messageType, message }) {
      return { type: messageType, payload: message };
    }
    
    export const getToken = (state) => state.dextaAuth.token;
    
    export function* setPost(action) {
      try {
        const token = yield select(getToken);
        const data = yield call(dextaApiService.post, '/api/savedAdvices', action.payload, token);
        yield put(
          openNotification({
            messageType: MessageBarType.success,
            message: 'Advice saved successfully',
          }),
        );
      } catch (err) {
        yield put(
          openNotification({
            messageType: MessageBarType.error,
            message: `Notification: ${err.message}`,
          }),
        );
      }
    }
    
    export function* savedAdvicesaga() {
      yield takeLatest('POST_SAVED_ADVICE', setPost);
    }
    

    dextaApiService.ts:

    export const dextaApiService = {
      async post(path, payload, token) {
        return 'real implementation';
      },
    };
    

    saga.test.ts:

    import { setPost } from './saga';
    import { dextaApiService } from './dextaApiService';
    import { runSaga } from 'redux-saga';
    import { Action } from 'redux';
    
    describe('67444295', () => {
      it('should pass', async () => {
        const dispatched: Action[] = [];
        const postSpy = jest.spyOn(dextaApiService, 'post').mockResolvedValueOnce('mocked response');
        const actual = await runSaga(
          {
            dispatch: (action: Action) => dispatched.push(action),
            getState: () => ({ dextaAuth: { token: 'abc123' } }),
          },
          setPost,
          { payload: 'mocked payload' },
        ).toPromise();
        expect(postSpy).toBeCalledWith('/api/savedAdvices', 'mocked payload', 'abc123');
        expect(dispatched).toEqual([{ type: 'success', payload: 'Advice saved successfully' }]);
        postSpy.mockRestore();
      });
    });
    

    test result:

     PASS  src/stackoverflow/67444295/saga.test.ts
      67444295
        ✓ should pass (5 ms)
    
    --------------------|---------|----------|---------|---------|-------------------
    File                | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    --------------------|---------|----------|---------|---------|-------------------
    All files           |   85.71 |        0 |      60 |      80 |                   
     dextaApiService.ts |      50 |      100 |       0 |      50 | 3                 
     saga.ts            |   89.47 |        0 |      75 |   84.62 | 26,36             
    --------------------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        2.567 s