【问题标题】:React unit test React sagaReact 单元测试 React 传奇
【发布时间】:2023-09-19 07:04:01
【问题描述】:

我在在 React Native 中测试 saga 时遇到了一些麻烦

我已经阅读了很多示例,但我似乎看不出什么是单元测试传奇的正确方法

例如:

我有以下传奇:

function* getUsers(action) {
  try {
    const { id } = action.payload;
    const { data } = yield call(api.users.getUsers, { id });

    yield put({ type: userConstants.LOAD_USERS, payload: data });

  } catch (err) {
    yield call(errorHandler, err);
    yield put({ type: userConstants.LOAD_USERS_FAIL });
  }
}

function* getProfiles(action) {
  try {
    const { id } = action.payload;
    const { data } = yield call(api.users.getProfiles, { id });

    yield put({ type: userConstants.LOAD_PROFILES, payload: data });

  } catch (err) {
    yield call(errorHandler, err);
    yield put({ type: userConstants.LOAD_PROFILES_FAIL });
  }
}


export function* usersSaga() {
  yield takeLatest(userConstants.GET_USERS, getUsers);
  yield takeLatest(userConstants.GET_USER_PROFILES, getProfiles);
  
}

测试文件:

import usersSaga from '../sagas/users/';

describe('Users Saga', () => {

it('should fetch users', () => {
   // what should I put in here
   // how should my test look like ?
});
})

我的测试应该是什么样子,我应该测试什么工作,有人可以给我一个基本的例子吗?

【问题讨论】:

    标签: javascript reactjs react-native unit-testing redux-saga


    【解决方案1】:

    您可以使用 Redux Saga Test Plan 来测试 watcher 和 worker sagas。

    对于单元测试,Redux Saga 测试计划导出一个 testSaga 函数,该函数创建一个模拟 saga 供您断言效果。

    例如

    saga.ts:

    import { call, put, takeLatest } from 'redux-saga/effects';
    import { api, errorHandler } from './api';
    import { userConstants } from './actionTypes';
    
    export function* getUsers(action) {
      try {
        const { id } = action.payload;
        const { data } = yield call(api.users.getUsers, { id });
    
        yield put({ type: userConstants.LOAD_USERS, payload: data });
      } catch (err) {
        yield call(errorHandler, err);
        yield put({ type: userConstants.LOAD_USERS_FAIL });
      }
    }
    
    export function* getProfiles(action) {
      try {
        const { id } = action.payload;
        const { data } = yield call(api.users.getProfiles, { id });
    
        yield put({ type: userConstants.LOAD_PROFILES, payload: data });
      } catch (err) {
        yield call(errorHandler, err);
        yield put({ type: userConstants.LOAD_PROFILES_FAIL });
      }
    }
    
    export function* usersSaga() {
      yield takeLatest(userConstants.GET_USERS, getUsers);
      yield takeLatest(userConstants.GET_USER_PROFILES, getProfiles);
    }
    

    actionTypes.ts:

    export const userConstants = {
      LOAD_USERS: 'LOAD_USERS',
      LOAD_USERS_FAIL: 'LOAD_USERS_FAIL',
      LOAD_PROFILES: 'LOAD_PROFILES',
      LOAD_PROFILES_FAIL: 'LOAD_PROFILES_FAIL',
      GET_USERS: 'GET_USERS',
      GET_USER_PROFILES: 'GET_USER_PROFILES',
    };
    

    api.ts:

    export const api = {
      users: {
        async getUsers(params) {},
        async getProfiles(params) {},
      },
    };
    
    export function errorHandler(err) {}
    

    saga.test.ts:

    import { testSaga } from 'redux-saga-test-plan';
    import { getProfiles, getUsers, usersSaga } from './saga';
    import { userConstants } from './actionTypes';
    import { api, errorHandler } from './api';
    
    describe('68294010', () => {
      describe('usersSaga', () => {
        it('should pass', () => {
          testSaga(usersSaga)
            .next()
            .takeLatest(userConstants.GET_USERS, getUsers)
            .next()
            .takeLatest(userConstants.GET_USER_PROFILES, getProfiles)
            .finish()
            .isDone();
        });
      });
    
      describe('getUsers', () => {
        it('should get data', () => {
          testSaga(getUsers, { payload: { id: 1 } })
            .next()
            .call(api.users.getUsers, { id: 1 })
            .next({ data: 'teresa teng' })
            .put({ type: userConstants.LOAD_USERS, payload: 'teresa teng' })
            .finish()
            .isDone();
        });
        it('should handle error', () => {
          const err = new Error('network');
          testSaga(getUsers, { payload: { id: 1 } })
            .next()
            .call(api.users.getUsers, { id: 1 })
            .throw(err)
            .call(errorHandler, err)
            .next()
            .put({ type: userConstants.LOAD_USERS_FAIL })
            .finish()
            .isDone();
        });
      });
    
      describe('getProfiles', () => {
        it('should get profile', () => {
          testSaga(getProfiles, { payload: { id: 1 } })
            .next()
            .call(api.users.getProfiles, { id: 1 })
            .next({ data: 'teresa teng' })
            .put({ type: userConstants.LOAD_PROFILES, payload: 'teresa teng' })
            .finish()
            .isDone();
        });
        it('should handle error', () => {
          const err = new Error('network');
          testSaga(getProfiles, { payload: { id: 1 } })
            .next()
            .call(api.users.getProfiles, { id: 1 })
            .throw(err)
            .call(errorHandler, err)
            .next()
            .put({ type: userConstants.LOAD_PROFILES_FAIL })
            .finish()
            .isDone();
        });
      });
    });
    

    单元测试结果:

     PASS  src/*/68294010/saga.test.ts
      68294010
        usersSaga
          ✓ should pass (3 ms)
        getUsers
          ✓ should get data (1 ms)
          ✓ should handle error (1 ms)
        getProfiles
          ✓ should get profile (1 ms)
          ✓ should handle error
    
    ----------------|---------|----------|---------|---------|-------------------
    File            | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ----------------|---------|----------|---------|---------|-------------------
    All files       |     100 |      100 |      50 |     100 |                   
     actionTypes.ts |     100 |      100 |     100 |     100 |                   
     api.ts         |     100 |      100 |       0 |     100 |                   
     saga.ts        |     100 |      100 |     100 |     100 |                   
    ----------------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       5 passed, 5 total
    Snapshots:   0 total
    Time:        4.04 s
    

    【讨论】: