Faire
Faire

Reputation: 1001

How to test api call in Redux Saga properly as a unit?

I want to test this saga API function:

export function* fetchEshopProjects(action: ProjectActionsGetEshopProjects) {
    yield put(projectActions.currentEshopProjectsLoading(true))

    const response: {
        projects: string[],
        error: Error
    } = yield call(ajax.json, '/new-project/eshops-projects/?eshopId=' + action.eshopId)

    if (response.error) {
        yield put(projectActions.currentEshopProjectsFailed(response.error))
        yield put(projectActions.currentEshopProjectsLoading(false))
    } else {
        yield put(projectActions.setCurrentEshopProjects(response.projects))
        yield put(projectActions.currentEshopProjectsLoading(false))
    }
}

I have written this test:

test('fetch projects', async() => {
        mockParams({
            locale: 'en-US',
        });

        ajax.json = jest.fn().mockResolvedValue(['kbp', 'tbp'])
        const dispatched = []
        await runSaga({
            dispatch: (action) => dispatched.push(action),
        },
            fetchEshopProjects
        )
        expect(dispatched).toEqual([
            {
                type: 'CURRENT_ESHOP_PROJECTS_LOADING',
                loadingState: true,
            },
            {
                type: 'SET_CURRENT_ESHOP_PROJECTS',
                payload: ['kbp', 'tbp'],
            },
            {
                type: 'CURRENT_ESHOP_PROJECTS_LOADING',
                loadingState: false,
            },
        ])
    })

It seems that the code only ever reaches the yield call(ajax.json... part and then returns back to to testing function - thus only the first dispatch is recorded. How can I correct the test so that it will conclude successfully?

Upvotes: 0

Views: 890

Answers (1)

Lin Du
Lin Du

Reputation: 102237

You forget to use task.toPromise for the task returned by runSaga. Below is an working example:

saga.ts:

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

export const projectActions = {
  currentEshopProjectsLoading(loadingState) {
    return { type: 'CURRENT_ESHOP_PROJECTS_LOADING', loadingState };
  },
  setCurrentEshopProjects(payload) {
    return { type: 'SET_CURRENT_ESHOP_PROJECTS', payload };
  },
  currentEshopProjectsFailed(error) {
    return { type: 'CURRENT_ESHOP_PROJECTS_FAILED', error };
  },
};

export function* fetchEshopProjects(action) {
  yield put(projectActions.currentEshopProjectsLoading(true));

  const response: {
    projects: string[];
    error: Error;
  } = yield call(ajax.json, '/new-project/eshops-projects/?eshopId=' + action.eshopId);

  if (response.error) {
    yield put(projectActions.currentEshopProjectsFailed(response.error));
    yield put(projectActions.currentEshopProjectsLoading(false));
  } else {
    yield put(projectActions.setCurrentEshopProjects(response.projects));
    yield put(projectActions.currentEshopProjectsLoading(false));
  }
}

ajax.ts:

export const ajax = {
  async json(url) {},
};

saga.test.ts:

import { runSaga } from 'redux-saga';
import { ajax } from './ajax';
import { fetchEshopProjects } from './saga';

test('fetch projects', async () => {
  ajax.json = jest.fn().mockResolvedValue({ projects: ['kbp', 'tbp'] });
  const dispatched: any[] = [];
  await runSaga(
    {
      dispatch: (action) => dispatched.push(action),
    },
    fetchEshopProjects as any,
    { eshopId: '123' },
  ).toPromise();
  expect(dispatched).toEqual([
    {
      type: 'CURRENT_ESHOP_PROJECTS_LOADING',
      loadingState: true,
    },
    {
      type: 'SET_CURRENT_ESHOP_PROJECTS',
      payload: ['kbp', 'tbp'],
    },
    {
      type: 'CURRENT_ESHOP_PROJECTS_LOADING',
      loadingState: false,
    },
  ]);
});

Test result:

 PASS   redux-saga-examples  packages/redux-saga-examples/src/stackoverflow/71176872/saga.test.ts
  ✓ fetch projects (6 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |   81.25 |       75 |      60 |   81.25 |                   
 ajax.ts  |     100 |      100 |       0 |     100 |                   
 saga.ts  |      80 |       75 |      75 |      80 | 12,25-26          
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.623 s, estimated 3 s

Upvotes: 1

Related Questions