Search code examples
testingaxiosjestjsmockingvuex

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


I am able to get a basic test working with Jest, but when I try to refactor it to use Jest's manual mocks features, the test no longer works.

Any ideas what I could be doing wrong?

Thank you for your time 🙏

error message:

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 |         },

in tests/.../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');
  });
});

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

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

export default mock;

in 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;

in 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;

I've tried looking at examples from these resources, but nothing worked for me:

mocking Axios Interceptors: Mocking axios with Jest throws error “Cannot read property 'interceptors' of undefined”
overriding mock implmentations:

Jest Mock Documentation: https://jestjs.io/docs/mock-function-api#mockfnmockimplementationfn
Jest Manual Mock documentation:

using 3rd party libraries: https://vhudyma-blog.eu/3-ways-to-mock-axios-in-jest/

simple actions test example: https://lmiller1990.github.io/vue-testing-handbook/vuex-actions.html#creating-the-action

outdated actions test example:


Solution

  • In case it helps others, I ended up just using spies and did not need to use manual mocks.

    These were references that helped me figure it out:

    https://silvenon.com/blog/mocking-with-jest/functions
    https://silvenon.com/blog/mocking-with-jest/modules/
    How to mock jest.spyOn for a specific axios call

    And here's the example code that ended up working for me:

    in tests/.../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');
      });
    });
    

    in 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;