如何重构我的测试以使用 Jest 的手动模拟功能?

How to refactor my test to use Jest's Manual Mock feature?

我能够使用 Jest 进行基本测试,但是当我尝试重构它以使用 Jest 的手动模拟功能时,测试不再有效。

知道我哪里做错了吗?

感谢您的宝贵时间

错误信息:

TypeError: _backendService.default.post is not a function

      16 |
      17 |     return $axios
    > 18 |       .post(`${RESOURCE_PATH}/batch_upload/`, formData, {
         |        ^
      19 |         headers: {
      20 |           "Content-Type": "multipart/form-data",
      21 |         },

在测试中/.../actions.spec.js:

//import $axios from "@/services/backend-service"; // could not get manual mock to work
import actions from "@/store/modules/transactions/actions";

//jest.mock("@/services/backend-service"); // could not get manual mock to work

// this bit of code works
jest.mock("@/services/backend-service", () => {
  return {
    post: jest.fn().mockResolvedValue(),
  };
});
// this bit of code works:end

describe("store/modules/transactions/actions", () => {
  it("uploads transactions succeeds", async() => {
    const state = {
      commit: jest.fn(),
    };

    await actions.uploadTransactions(
      state,
      {'file': 'arbitrary filename'}
    )

    expect(state.commit).toHaveBeenCalledWith('changeUploadStatusToSucceeded');
  });
});

在 src/.../__mocks__/backend-service.js:

const mock = jest.fn().mockImplementation(() => {
  return {
    post: jest.fn().mockResolvedValue(),
  };
});

export default mock;

在 src/.../backend-service.js:

import axios from "axios";

const API_BASE_URL =
  `${process.env["VUE_APP_BACKEND_SCHEME"]}` +
  `://` +
  `${process.env["VUE_APP_BACKEND_HOST"]}` +
  `:` +
  `${process.env["VUE_APP_BACKEND_PORT"]}` +
  `/` +
  `${process.env["VUE_APP_BACKEND_PATH_PREFIX"]}`;

const $axios = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    "Content-Type": "application/vnd.api+json",
  },
});

export default $axios;

在 src/.../actions.js:

import $axios from "@/services/backend-service";

const RESOURCE_NAME = "transaction";
const RESOURCE_PATH = `${RESOURCE_NAME}s`;

export const actions = {
  uploadTransactions(state, payload) {
    let formData = new FormData();
    formData.append("file", payload["file"]);

    return $axios
      .post(`${RESOURCE_PATH}/batch_upload/`, formData, {
        headers: {
          "Content-Type": "multipart/form-data",
        },
      })
      .then((response) => {
        state.commit("changeUploadStatusToSucceeded");
      })
      .catch(function (error) {
        if (error.response) {
          state.commit("changeUploadStatusToFailed");
        }
      });
  },
};

export default actions;

我尝试查看这些资源中的示例,但对我没有任何帮助:

模拟 Axios 拦截器:
覆盖模拟实现:

Jest 模拟文档:https://jestjs.io/docs/mock-function-api#mockfnmockimplementationfn
Jest 手册模拟文档:

使用第 3 方库:https://vhudyma-blog.eu/3-ways-to-mock-axios-in-jest/

简单动作测试示例:https://lmiller1990.github.io/vue-testing-handbook/vuex-actions.html#creating-the-action

过时的动作测试示例:

为了帮助其他人,我最终只使用间谍而不需要使用手动模拟。

这些是帮助我弄明白的参考资料:

https://silvenon.com/blog/mocking-with-jest/functions
https://silvenon.com/blog/mocking-with-jest/modules/

下面是最终为我工作的示例代码:

在测试中/.../actions.spec.js:

import $axios from "@/services/backend-service";
import actions from "@/store/modules/transactions/actions";

describe("store/modules/transactions/actions", () => {
  let state;
  let postSpy;
  beforeEach(() => {
    state = {
      commit: jest.fn(),
    };
    postSpy = jest.spyOn($axios, 'post')
  });

  it("uploads transactions succeeds", async() => {
    postSpy.mockImplementation(() => {
      return Promise.resolve();
    });

    await actions.uploadTransactions(
      state,
      {'file': 'arbitrary filename'},
    )

    expect(state.commit).toHaveBeenCalledWith('changeUploadStatusToSucceeded');
  });

  it("uploads transactions fails", async() => {
    postSpy.mockImplementation(() => {
      return Promise.reject({
        response: true,
      });
    });

    await actions.uploadTransactions(
      state,
      {'file': 'arbitrary filename'},
    )

    expect(state.commit).toHaveBeenCalledWith('changeUploadStatusToFailed');
  });
});

在 src/.../actions.js:

import $axios from "@/services/backend-service";

const RESOURCE_NAME = "transaction";
const RESOURCE_PATH = `${RESOURCE_NAME}s`;

export const actions = {
  uploadTransactions(state, payload) {
    let formData = new FormData();
    formData.append("account_id", 1); // change to get dynamically when ready
    formData.append("file", payload["file"]);

    //$axios.interceptors.request.use(function (config) {
    //  state.commit("changeUploadStatusToUploading");
    //  return config;
    //});

    return $axios
      .post(`${RESOURCE_PATH}/batch_upload/`, formData, {
        headers: {
          "Content-Type": "multipart/form-data",
        },
      })
      .then((response) => {
        console.log(response);
        state.commit("changeUploadStatusToSucceeded");
      })
      .catch(function (error) {
        if (error.response) {
          state.commit("changeUploadStatusToFailed");
        }
      });
  },
};

export default actions;