Example 1 :- Mocking Classes
//returnNameOrAge.js
export const returnNameOrAge = (isName) => {
if (isName) {
return "My Name is XYZ"
}
return 25
}
//someOtherFunction.js
class SomeModule {
returnTrueOrFalse() {
return false;
}
}
export { SomeModule };
//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;
};
//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);
});
});
let's break down the modules and the test file
Modules:
returnNameOrAge.js: This module defines a simple function named
returnNameOrAgethat takes a boolean (isName) as input. IfisNameis true, it returns a string "My Name is XYZ". Otherwise, it returns the number 25.someOtherFunctions.js: This module defines a class named
SomeModule. The class has a method calledreturnTrueOrFalsethat simply returnsfalse. This class is likely intended to be mocked in the test.index.js: This is the main module where everything comes together. It imports the
returnNameOrAgefunction and theSomeModuleclass from the other modules. It then creates a new instance ofSomeModuleand defines a function calledtestVitest. ThetestVitestfunction calls thereturnTrueOrFalsemethod of thesomeModuleinstance and then uses the return value to call thereturnNameOrAgefunction. Finally, it returns the result fromreturnNameOrAge.
Test File (index.test.js):
This file uses the Vitest testing framework to test the testVitest function from index.js.
Imports: It imports the necessary functions from Vitest (
it,expect,describe, andvi) for writing tests. It also imports thetestVitestfunction fromindex.jsand theSomeModuleclass fromsomeOtherFunctions.js.Mocking: It uses the
vi.mockfunction to mock thesomeOtherFunctions.jsmodule. This means that when the test runs, it won't use the actual implementation of theSomeModuleclass, but a mocked version instead. The mocked version is defined using a function that returns an object with a mockedSomeModuleclass. The mockedSomeModuleclass has a mockedreturnTrueOrFalsemethod that can be controlled by the test.-
Test Cases: The file defines two test cases using
describeandit.- The first test case (
it("should return name")) mocks thereturnTrueOrFalsemethod of the mockedSomeModuleto returntrue. It then calls thetestVitestfunction and asserts that the returned value is "My Name is XYZ" usingexpect. - The second test case (
it("should return age")) mocks thereturnTrueOrFalsemethod of the mockedSomeModuleto returnfalse. It then calls thetestVitestfunction and asserts that the returned value is 25 usingexpect.
- The first test case (
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
testVitestinindex.js. This function relies onreturnNameOrAgeto format the output based on the value received fromSomeModule. SincereturnNameOrAgeis a simple function with clear logic, mocking it might be unnecessary for this specific test.Mocking Complexity: Mocking a class like
SomeModuleallows 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 ofreturnTrueOrFalseto verify howtestVitesthandles different inputs. MockingreturnNameOrAgewouldn't provide the same level of control.Testing vs. Implementation: Ideally, the functionality of
returnNameOrAgeshould 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 oftestVitest.
Example 2 :- Mocking Functions
//returnNameOrAge.js
export const returnNameOrAge = (isName) => {
if (isName) {
return "My Name is XYZ"
}
return 25
}
//someOtherFunction.js
export const someOtherFunction = () => {
const testValue = true;
return testValue;
};
//index.js
import { returnNameOrAge } from "./returnNameOrAge";
import { someOtherFunction } from "./someOtherFunctions";
export const testVitest = () => {
const dataFromSomeOtherFunction = someOtherFunction();
const dataFromReturnNameOrAge = returnNameOrAge(dataFromSomeOtherFunction);
return dataFromReturnNameOrAge;
};
//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);
});
});
let's break down the modules and the test file
Modules:
-
returnNameOrAge.js:
- This module defines a function named
returnNameOrAgethat takes a boolean (isName) as input. - If
isNameis true, it returns the string "My Name is XYZ". - Otherwise, it returns the number 25.
- This module defines a function named
-
someOtherFunctions.js:
- This module defines a function named
someOtherFunction. - This function simply creates a constant
testValuewith the valuetrueand returns it.
- This module defines a function named
-
index.js:
- This is the main module.
- It imports
returnNameOrAgefromreturnNameOrAge.jsandsomeOtherFunctionfromsomeOtherFunctions.js. - It defines a function called
testVitest. -
testVitestcallssomeOtherFunctionto get a value. - It then calls
returnNameOrAgewith the value fromsomeOtherFunctionand returns the result.
Test File (index.test.js):
This file uses the Vitest testing framework to test the testVitest function from index.js.
-
Imports:
- It imports necessary functions from Vitest for writing tests (
it,expect,describe, andvi). - It imports the
testVitestfunction fromindex.js.
- It imports necessary functions from Vitest for writing tests (
-
Mocking:
- It uses the
vi.mockandvi.hoistedfunctions together to mock thesomeOtherFunction. Here's a breakdown:-
vi.hoistedcreates a function that returns an object to hold mocks. This ensures mocks are created only once before each test. - Inside
vi.mock, it replaces thesomeOtherFunctionfromsomeOtherFunctions.jswith the mocked version from themocksobject.
-
- It uses the
-
Test Cases:
- The file defines two test cases using
describeandit.- The first test (
"should return name") mockssomeOtherFunctionto returntrue(usingmockReturnValue). It then callstestVitestand asserts the returned value is "My Name is XYZ" usingexpect. - The second test (
"should return age") mockssomeOtherFunctionto returnfalse. It then callstestVitestand asserts the returned value is 25 usingexpect.
- The first test (
- The file defines two test cases using
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;
// 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);
});
// 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);
});
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,
},
};
});
KEY TAKE AWAYS
general use cases of
importActual:- class mocking, function mocking especially when these functions are spied on {vi.spyOn()} (good use cases example forspyOnwould be to unit test how many times the function has been executed)use case of
hoisting+mocking:- for general test cases
Top comments (0)