javascript - 使用 Jest 测试 Redux 和 Axios 获取中间件

标签 javascript reactjs typescript redux jestjs

我有一个中间件,可以拦截具有以下类型的任何操作:API_REQUEST。具有 API_REQUEST 的操作由 apiRequest() 操作创建者创建。当我的中间件拦截一个操作时,它会使用 Axios 发出请求,如果请求成功,它会分派(dispatch)由 apiSuccess() 创建的操作。如果 Axios 在请求后抛出,中间件将调度使用 apiError() 创建的操作。

中间件:

const apiMiddleware: Middleware = ({ dispatch }) => next => async (action): Promise<void> => {
    next(action);

    if (action.type.includes(API_REQUEST)) {
        const body = action.payload;
        const { url, method, feature } = action.meta;

        try {
            const response = await axios({ method, url, data: body });
            dispatch(apiSuccess({ response, feature }));
        } catch (error) {
            console.error(error);
            dispatch(apiError({ error, feature }));
        }
    }
};

这就是我的 api 中间件的工作原理。

现在我想知道如何使用 Jest 来测试它。也许我可以模拟 Axios,以便它在中间件中发出虚假请求,但如何实现呢?

这是我当前的测试文件:

describe('api middleware', () => {
    const feature = 'test_feat';

    it('calls next', () => {
        const { invoke, next } = create(apiMiddleware);
        const action = { type: 'TEST' };

        invoke(action);

        expect(next).toHaveBeenCalledWith(action);
    });

    it('dispatch api success on request success', () => {
        const { invoke, next, store } = create(apiMiddleware);
        const action = actions.apiRequest({ body: null, method: 'GET', url: '', feature });
        const data = { test: 'test data' };

        jest.mock('axios');

        invoke(action);

        expect(next).toHaveBeenCalledWith(action);

        expect(store.dispatch).toHaveBeenCalledWith(actions.apiSuccess({
            response: axios.mockResolvedValue({ data }),
            feature,
        }));
    });
});

create() 只是我从 this part of the doc 中获取的一个函数。 。它允许我模拟 dispatchgetStatenext

显然这行不通,但我确信有办法。

最佳答案

这是单元测试解决方案:

api.middleware.ts:

import { Middleware } from 'redux';
import axios from 'axios';
import { API_REQUEST } from './actionTypes';
import { apiSuccess, apiError } from './actionCreator';

export const apiMiddleware: Middleware = ({ dispatch }) => (next) => async (action): Promise<void> => {
  next(action);

  if (action.type.includes(API_REQUEST)) {
    const body = action.payload;
    const { url, method, feature } = action.meta;

    try {
      const response = await axios({ method, url, data: body });
      dispatch(apiSuccess({ response, feature }));
    } catch (error) {
      console.error(error);
      dispatch(apiError({ error, feature }));
    }
  }
};

actionTypes.ts:

export const API_REQUEST = 'API_REQUEST';
export const API_REQUEST_SUCCESS = 'API_REQUEST_SUCCESS';
export const API_REQUEST_FAILURE = 'API_REQUEST_FAILURE';

actionCreator.ts:

import { API_REQUEST_SUCCESS, API_REQUEST_FAILURE } from './actionTypes';

export function apiSuccess(data) {
  return {
    type: API_REQUEST_SUCCESS,
    ...data,
  };
}
export function apiError(data) {
  return {
    type: API_REQUEST_FAILURE,
    ...data,
  };
}

api.middleware.test.ts:

import { apiMiddleware } from './api.middleware';
import axios from 'axios';
import { MiddlewareAPI } from 'redux';
import { API_REQUEST, API_REQUEST_SUCCESS, API_REQUEST_FAILURE } from './actionTypes';

jest.mock('axios', () => jest.fn());

describe('59754838', () => {
  afterEach(() => {
    jest.clearAllMocks();
  });
  describe('#apiMiddleware', () => {
    describe('Unit test', () => {
      it('should dispatch api success action', async () => {
        const store: MiddlewareAPI = { dispatch: jest.fn(), getState: jest.fn() };
        const next = jest.fn();
        const action = {
          type: API_REQUEST,
          payload: {},
          meta: { url: 'http://localhost', method: 'get', feature: 'feature' },
        };
        const mResponse = { name: 'user name' };
        (axios as jest.Mocked<any>).mockResolvedValueOnce(mResponse);
        await apiMiddleware(store)(next)(action);
        expect(next).toBeCalledWith(action);
        expect(axios).toBeCalledWith({ method: action.meta.method, url: action.meta.url, data: action.payload });
        expect(store.dispatch).toBeCalledWith({
          type: API_REQUEST_SUCCESS,
          response: mResponse,
          feature: action.meta.feature,
        });
      });

      it('should dispatch api error action', async () => {
        const store: MiddlewareAPI = { dispatch: jest.fn(), getState: jest.fn() };
        const next = jest.fn();
        const action = {
          type: API_REQUEST,
          payload: {},
          meta: { url: 'http://localhost', method: 'get', feature: 'feature' },
        };
        const mError = new Error('network error');
        (axios as jest.Mocked<any>).mockRejectedValueOnce(mError);
        await apiMiddleware(store)(next)(action);
        expect(next).toBeCalledWith(action);
        expect(axios).toBeCalledWith({ method: action.meta.method, url: action.meta.url, data: action.payload });
        expect(store.dispatch).toBeCalledWith({
          type: API_REQUEST_FAILURE,
          error: mError,
          feature: action.meta.feature,
        });
      });
    });
  });
});

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

 PASS  src/stackoverflow/59754838/api.middleware.test.ts (11.206s)
  59754838
    #apiMiddleware
      Unit test
        ✓ should dispatch api success action (21ms)
        ✓ should dispatch api error action (23ms)

  console.error src/stackoverflow/59754838/api.middleware.ts:3460
    Error: network error
        at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59754838/api.middleware.test.ts:42:24
        at step (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59754838/api.middleware.test.ts:33:23)
        at Object.next (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59754838/api.middleware.test.ts:14:53)
        at /Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59754838/api.middleware.test.ts:8:71
        at new Promise (<anonymous>)
        at Object.<anonymous>.__awaiter (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59754838/api.middleware.test.ts:4:12)
        at Object.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/src/stackoverflow/59754838/api.middleware.test.ts:34:46)
        at Object.asyncJestTest (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:102:37)
        at resolve (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
        at new Promise (<anonymous>)
        at mapper (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
        at promise.then (/Users/ldu020/workspace/github.com/mrdulin/jest-codelab/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
        at process._tickCallback (internal/process/next_tick.js:68:7)

-------------------|----------|----------|----------|----------|-------------------|
File               |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files          |      100 |       50 |      100 |      100 |                   |
 actionCreator.ts  |      100 |      100 |      100 |      100 |                   |
 actionTypes.ts    |      100 |      100 |      100 |      100 |                   |
 api.middleware.ts |      100 |       50 |      100 |      100 |                 9 |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        12.901s

源代码:https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59754838

关于javascript - 使用 Jest 测试 Redux 和 Axios 获取中间件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59754838/

相关文章:

javascript - 动画在 react-spring 中过渡时的组件捕捉效果?

angular - 如何对这个 Angular typescript Http Error Interceptor 进行单元测试,该拦截器从管道 observable 中捕获错误?

angular - 取消选择单选按钮 Angular 2?

php - 如果您刷新 AJAX 请求的内容 (ob_flush),该内容将被加载?

javascript - Google-Chart-angularjs 是否支持 "date"类型?

javascript - 下拉选择更改时更新状态 React

javascript - 使用react-datepicker仅选择并更新选定div中的日期

typescript :导出命名空间中的所有函数

javascript - three.js Raycaster 似乎一直在动画的第一个关键帧的基础上工作

javascript - 如何使用 JS eventListener 提交表单来更新 Firestore 数据库