首页 > 解决方案 > 在 React Saga 单元测试中需要帮助

问题描述

我在测试我的传奇功能时遇到了一个问题:

function * onSaveDATA() {
  try {
    yield put( showStatusMessage({ messageContent: 'Saving Your Data' }));
    const body = yield select( state => state.appData.userDetails ); 
    yield call( postDATA, { body });
    yield put( hideStatusMessage());
    yield put({ type: ActionTypes.SAVE_DATA_OK });
  } catch ( e ) {
    yield put({ type: ActionTypes.CRITICAL_ERROR_OCCURED, payload: e });
  }
}

export function * save_on_change( ) {
  yield takeEvery( ActionTypes.SAVE_DATA_REQ, onSaveDATA );
}

这是我为测试此功能而编写的单元测试,但测试失败。我不确定出了什么问题。

import { runSaga } from 'redux-saga';

import { postDATA } from './../../../../services/my_service';

// import { openModalMessage } from './../../../../actions';

import { saveDATA } from './';

jest.mock( './../../../../services/my_service' );
jest.mock( './../../../../actions' );


describe( 'Saga: Save Data', () => {
  test( 'saveDATA OK', async () => {

    postDATA.mockReset();
    postDATA.mockReturnValue( {s:'Somevalue'} );
    const dispatchedActions = [];
    await runSaga({
      dispatch: action => dispatchedActions.push( action ),
      getState: () => ({
        appState: {},
        appData: { userDetails: {name:'mock-name'}},
      }),
    }, save_on_change );
    expect( postDATA ).toHaveBeenCalled();
      });
});

当我运行它时,它失败了。我不确定我在这里错过了什么。是不是因为该saveDATA函数使用工厂函数takeEvery。我是否需要明确触发 SAVE_DATA_REQ 操作?

标签: javascriptreactjsunit-testingjestjsredux-saga

解决方案


这是单元测试解决方案"redux-saga": "^1.1.3"

index.ts

import { put, select, call, takeEvery } from 'redux-saga/effects';
import { postDATA } from './service';

export const ActionTypes = {
  SAVE_DATA_OK: 'SAVE_DATA_OK',
  CRITICAL_ERROR_OCCURED: 'CRITICAL_ERROR_OCCURED',
  SAVE_DATA_REQ: 'SAVE_DATA_REQ',
};

const showStatusMessage = (payload) => ({ type: 'SHOW_STATUS_MESSAGE', payload });
const hideStatusMessage = () => ({ type: 'HIDE_STATUS_MESSAGE' });

export function* onSaveDATA() {
  try {
    yield put(showStatusMessage({ messageContent: 'Saving Your Data' }));
    const body = yield select((state) => state.appData.userDetails);
    yield call(postDATA, { body });
    yield put(hideStatusMessage());
    yield put({ type: ActionTypes.SAVE_DATA_OK });
  } catch (e) {
    yield put({ type: ActionTypes.CRITICAL_ERROR_OCCURED, payload: e });
  }
}

export function* save_on_change() {
  yield takeEvery(ActionTypes.SAVE_DATA_REQ, onSaveDATA);
}

service.ts

export async function postDATA(data) {
  return { s: 'real data' };
}

index.test.ts

import { runSaga } from 'redux-saga';
import { onSaveDATA, ActionTypes, save_on_change } from './';
import { postDATA } from './service';
import { mocked } from 'ts-jest/utils';
import { takeEvery } from 'redux-saga/effects';

jest.mock('./service');

describe('62952662', () => {
  afterAll(() => {
    jest.resetAllMocks();
  });
  describe('onSaveDATA', () => {
    test('should save data', async () => {
      mocked(postDATA).mockResolvedValueOnce({ s: 'Somevalue' });
      const dispatchedActions: any[] = [];
      await runSaga(
        {
          dispatch: (action) => dispatchedActions.push(action),
          getState: () => ({
            appState: {},
            appData: { userDetails: { name: 'mock-name' } },
          }),
        },
        onSaveDATA,
      ).toPromise();
      expect(postDATA).toBeCalledWith({ body: { name: 'mock-name' } });
      expect(dispatchedActions).toEqual([
        { type: 'SHOW_STATUS_MESSAGE', payload: { messageContent: 'Saving Your Data' } },
        { type: 'HIDE_STATUS_MESSAGE' },
        { type: ActionTypes.SAVE_DATA_OK },
      ]);
    });

    test('should handle error if postDATA error', async () => {
      const mError = new Error('network');
      mocked(postDATA).mockRejectedValueOnce(mError);
      const dispatchedActions: any[] = [];
      await runSaga(
        {
          dispatch: (action) => dispatchedActions.push(action),
          getState: () => ({
            appState: {},
            appData: { userDetails: { name: 'mock-name' } },
          }),
        },
        onSaveDATA,
      ).toPromise();
      expect(postDATA).toBeCalledWith({ body: { name: 'mock-name' } });
      expect(dispatchedActions).toEqual([
        { type: 'SHOW_STATUS_MESSAGE', payload: { messageContent: 'Saving Your Data' } },
        { type: ActionTypes.CRITICAL_ERROR_OCCURED, payload: mError },
      ]);
    });
  });

  describe('save_on_change', () => {
    test('should wait for every SAVE_DATA_REQ action and call onSaveDATA', () => {
      const gen = save_on_change();
      expect(gen.next().value).toEqual(takeEvery(ActionTypes.SAVE_DATA_REQ, onSaveDATA));
      expect(gen.next().done).toBeTruthy();
    });
  });
});

带有覆盖率报告的单元测试结果:

 PASS  src/stackoverflow/62952662/index.test.ts
  62952662
    onSaveDATA
      ✓ should save data (6 ms)
      ✓ should handle error if postDATA error (2 ms)
    save_on_change
      ✓ should wait for every SAVE_DATA_REQ action and call onSaveDATA (1 ms)

------------|---------|----------|---------|---------|-------------------
File        | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------|---------|----------|---------|---------|-------------------
All files   |      95 |      100 |   83.33 |   93.75 |                   
 index.ts   |     100 |      100 |     100 |     100 |                   
 service.ts |      50 |      100 |       0 |      50 | 2                 
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        2.928 s, estimated 3 s

推荐阅读