DEV Community

Cover image for 4. Some More Examples and Explanations
Sandheep Kumar Patro
Sandheep Kumar Patro

Posted on

4. Some More Examples and Explanations

Example 1 :- Mocking Classes

//returnNameOrAge.js
export const returnNameOrAge = (isName) => {
  if (isName) {
    return "My Name is XYZ"
  }

  return 25
}
Enter fullscreen mode Exit fullscreen mode
//someOtherFunction.js
class SomeModule {
  returnTrueOrFalse() {
    return false;
  }
}

export { SomeModule };
Enter fullscreen mode Exit fullscreen mode
//index.js
import { returnNameOrAge } from "./returnNameOrAge";
import { SomeModule } from "./someOtherFunctions";

const someModule = new SomeModule();

export const testVitest = () => {
  const dataFromSomeModule = someModule.returnTrueOrFalse();

  const dataFromReturnNameOrAge = returnNameOrAge(dataFromSomeModule);

  return dataFromReturnNameOrAge;
};
Enter fullscreen mode Exit fullscreen mode
//index.test.js
import { it, expect, describe, vi } from "vitest";
import { testVitest } from ".";
import { SomeModule } from "./someOtherFunctions";

vi.mock("./someOtherFunctions.js", async (importActual) => {
  const mod = await importActual();
  const SomeModule = vi.fn().mockReturnValue({
    returnTrueOrFalse: vi.fn(),
  });

  return {
    ...mod,
    SomeModule,
  };
});

describe("testVitest", () => {
  it("should return name", () => {
    const mockedSomeModule = new SomeModule();

    vi.mocked(mockedSomeModule.returnTrueOrFalse).mockReturnValue(true);

    const data = testVitest();

    expect(data).toBe("My Name is XYZ");
  });

  it("should return age", () => {
    const mockedSomeModule = new SomeModule();

    vi.mocked(mockedSomeModule.returnTrueOrFalse).mockReturnValue(false);

    const data = testVitest();

    expect(data).toBe(25);
  });
});
Enter fullscreen mode Exit fullscreen mode

let's break down the modules and the test file

Modules:

  1. returnNameOrAge.js: This module defines a simple function named returnNameOrAge that takes a boolean (isName) as input. If isName is true, it returns a string "My Name is XYZ". Otherwise, it returns the number 25.

  2. someOtherFunctions.js: This module defines a class named SomeModule. The class has a method called returnTrueOrFalse that simply returns false. This class is likely intended to be mocked in the test.

  3. index.js: This is the main module where everything comes together. It imports the returnNameOrAge function and the SomeModule class from the other modules. It then creates a new instance of SomeModule and defines a function called testVitest. The testVitest function calls the returnTrueOrFalse method of the someModule instance and then uses the return value to call the returnNameOrAge function. Finally, it returns the result from returnNameOrAge.

Test File (index.test.js):

This file uses the Vitest testing framework to test the testVitest function from index.js.

  1. Imports: It imports the necessary functions from Vitest (it, expect, describe, and vi) for writing tests. It also imports the testVitest function from index.js and the SomeModule class from someOtherFunctions.js.

  2. Mocking: It uses the vi.mock function to mock the someOtherFunctions.js module. This means that when the test runs, it won't use the actual implementation of the SomeModule class, but a mocked version instead. The mocked version is defined using a function that returns an object with a mocked SomeModule class. The mocked SomeModule class has a mocked returnTrueOrFalse method that can be controlled by the test.

  3. Test Cases: The file defines two test cases using describe and it.

    • The first test case (it("should return name")) mocks the returnTrueOrFalse method of the mocked SomeModule to return true. It then calls the testVitest function and asserts that the returned value is "My Name is XYZ" using expect.
    • The second test case (it("should return age")) mocks the returnTrueOrFalse method of the mocked SomeModule to return false. It then calls the testVitest function and asserts that the returned value is 25 using expect.

MAY I HAVE YOUR ATTENTION, PLEASE!!!

Even though index.js depends on both returnNameOrAge and SomeModule only SomeModule but not returnNameOrAge is mocked in the test, here are the reason :-

  • Focus of the Test: The test aims to isolate the logic of testVitest in index.js. This function relies on returnNameOrAge to format the output based on the value received from SomeModule. Since returnNameOrAge is a simple function with clear logic, mocking it might be unnecessary for this specific test.

  • Mocking Complexity: Mocking a class like SomeModule allows for more control over its behavior. You can define what its methods return in different scenarios. In this case, the test wants to control the output of returnTrueOrFalse to verify how testVitest handles different inputs. Mocking returnNameOrAge wouldn't provide the same level of control.

  • Testing vs. Implementation: Ideally, the functionality of returnNameOrAge should be tested in its own dedicated test file. This keeps the tests focused and avoids redundancy. Mocking it in this test would be testing an implementation detail rather than the overall logic of testVitest.

Example 2 :- Mocking Functions

//returnNameOrAge.js
export const returnNameOrAge = (isName) => {
  if (isName) {
    return "My Name is XYZ"
  }

  return 25
}
Enter fullscreen mode Exit fullscreen mode
//someOtherFunction.js
export const someOtherFunction = () => {
  const testValue = true;

  return testValue;
};
Enter fullscreen mode Exit fullscreen mode
//index.js
import { returnNameOrAge } from "./returnNameOrAge";
import { someOtherFunction } from "./someOtherFunctions";

export const testVitest = () => {
  const dataFromSomeOtherFunction = someOtherFunction();

  const dataFromReturnNameOrAge = returnNameOrAge(dataFromSomeOtherFunction);

  return dataFromReturnNameOrAge;
};
Enter fullscreen mode Exit fullscreen mode
//index.test.js
import { it, expect, describe, vi } from "vitest";
import { testVitest } from ".";

const mocks = vi.hoisted(() => ({
  someOtherFunction: vi.fn(),
}));

vi.mock("./someOtherFunctions.js", () => ({
  someOtherFunction: mocks.someOtherFunction,
}));

describe("testVitest", () => {
  it("should return name", () => {
    mocks.someOtherFunction.mockReturnValue(true);
    const data = testVitest();

    expect(data).toBe("My Name is XYZ");
  });

  it("should return age", () => {
    mocks.someOtherFunction.mockReturnValue(false);

    const data = testVitest();

    expect(data).toBe(25);
  });
});
Enter fullscreen mode Exit fullscreen mode

let's break down the modules and the test file

Modules:

  1. returnNameOrAge.js:
    • This module defines a function named returnNameOrAge that takes a boolean (isName) as input.
    • If isName is true, it returns the string "My Name is XYZ".
    • Otherwise, it returns the number 25.
  2. someOtherFunctions.js:
    • This module defines a function named someOtherFunction.
    • This function simply creates a constant testValue with the value true and returns it.
  3. index.js:
    • This is the main module.
    • It imports returnNameOrAge from returnNameOrAge.js and someOtherFunction from someOtherFunctions.js.
    • It defines a function called testVitest.
    • testVitest calls someOtherFunction to get a value.
    • It then calls returnNameOrAge with the value from someOtherFunction and returns the result.

Test File (index.test.js):

This file uses the Vitest testing framework to test the testVitest function from index.js.

  1. Imports:
    • It imports necessary functions from Vitest for writing tests (it, expect, describe, and vi).
    • It imports the testVitest function from index.js.
  2. Mocking:

    • It uses the vi.mock and vi.hoisted functions together to mock the someOtherFunction. Here's a breakdown:
      • vi.hoisted creates a function that returns an object to hold mocks. This ensures mocks are created only once before each test.
      • Inside vi.mock, it replaces the someOtherFunction from someOtherFunctions.js with the mocked version from the mocks object.
  3. Test Cases:

    • The file defines two test cases using describe and it.
      • The first test ("should return name") mocks someOtherFunction to return true (using mockReturnValue). It then calls testVitest and asserts the returned value is "My Name is XYZ" using expect.
      • The second test ("should return age") mocks someOtherFunction to return false. It then calls testVitest and asserts the returned value is 25 using expect.

Example 3 :- Actual Import Mocking and/or Hoisting+Mocking

Now, We'll write two unit tests for the same function, each leveraging a different mocking approach. The first test case will demonstrate mocking the actual import, while the second will explore the concept of hoisting combined with mocking. By comparing these techniques, you'll gain valuable insights into their strengths and how to choose the most suitable method for your specific testing needs.

// userService.js
import axios from "axios";

const userService = async () => {
  const { data } = await axios.get(
    "https://jsonplaceholder.typicode.com/users/1",
  );

  return data;
};

export default userService;
Enter fullscreen mode Exit fullscreen mode
// approach involving hoisting and mocking (this approach doesn't involve importing axios in the test file)
import userService from "./userService";

const mockedAxios = vi.hoisted(() => ({
  default: {
    get: vi.fn(),
  },
}));

vi.mock("axios", () => ({
  default: mockedAxios.default,
}));

test("testing", async () => {
  mockedAxios.default.get.mockResolvedValue({
    data: {
      id: 1,
    },
  });

  const data = await userService();

  expect(data.id).toBe(1);
});
Enter fullscreen mode Exit fullscreen mode
// approach involving mocking using importActual (this approach involve importing axios in the test file)
import axios from "axios";
import userService from "./userService";

vi.mock("axios", async () => {
  const axios = await vi.importActual("axios");
  const get = vi.fn();

  return {
    default: {
      ...axios,
      get,
    },
  };
});

test("testing", async () => {
  vi.mocked(axios.get).mockResolvedValue({
    data: {
      id: 1,
    },
  });
  const data = await userService();

  expect(data.id).toBe(1);
});
Enter fullscreen mode Exit fullscreen mode

importActual can also be used as following as well

vi.mock("axios", async (importActual) => {
  const axios = await importActual<typeof import("axios")>();
  const get = vi.fn();

  return {
    default: {
      ...axios,
      get,
    },
  };
});
Enter fullscreen mode Exit fullscreen mode

KEY TAKE AWAYS

  1. general use cases ofimportActual :- class mocking, function mocking especially when these functions are spied on {vi.spyOn()} (good use cases example for spyOn would be to unit test how many times the function has been executed)

  2. use case of hoisting+mocking :- for general test cases

Top comments (0)