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
returnNameOrAge
that takes a boolean (isName
) as input. IfisName
is 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 calledreturnTrueOrFalse
that 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
returnNameOrAge
function and theSomeModule
class from the other modules. It then creates a new instance ofSomeModule
and defines a function calledtestVitest
. ThetestVitest
function calls thereturnTrueOrFalse
method of thesomeModule
instance and then uses the return value to call thereturnNameOrAge
function. 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 thetestVitest
function fromindex.js
and theSomeModule
class fromsomeOtherFunctions.js
.Mocking: It uses the
vi.mock
function to mock thesomeOtherFunctions.js
module. This means that when the test runs, it won't use the actual implementation of theSomeModule
class, but a mocked version instead. The mocked version is defined using a function that returns an object with a mockedSomeModule
class. The mockedSomeModule
class has a mockedreturnTrueOrFalse
method that can be controlled by the test.-
Test Cases: The file defines two test cases using
describe
andit
.- The first test case (
it("should return name")
) mocks thereturnTrueOrFalse
method of the mockedSomeModule
to returntrue
. It then calls thetestVitest
function and asserts that the returned value is "My Name is XYZ" usingexpect
. - The second test case (
it("should return age")
) mocks thereturnTrueOrFalse
method of the mockedSomeModule
to returnfalse
. It then calls thetestVitest
function 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
testVitest
inindex.js
. This function relies onreturnNameOrAge
to format the output based on the value received fromSomeModule
. SincereturnNameOrAge
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 ofreturnTrueOrFalse
to verify howtestVitest
handles different inputs. MockingreturnNameOrAge
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 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
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.
- This module defines a function named
-
someOtherFunctions.js:
- This module defines a function named
someOtherFunction
. - This function simply creates a constant
testValue
with the valuetrue
and returns it.
- This module defines a function named
-
index.js:
- This is the main module.
- It imports
returnNameOrAge
fromreturnNameOrAge.js
andsomeOtherFunction
fromsomeOtherFunctions.js
. - It defines a function called
testVitest
. -
testVitest
callssomeOtherFunction
to get a value. - It then calls
returnNameOrAge
with the value fromsomeOtherFunction
and 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
testVitest
function fromindex.js
.
- It imports necessary functions from Vitest for writing tests (
-
Mocking:
- It uses the
vi.mock
andvi.hoisted
functions together to mock thesomeOtherFunction
. 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 thesomeOtherFunction
fromsomeOtherFunctions.js
with the mocked version from themocks
object.
-
- It uses the
-
Test Cases:
- The file defines two test cases using
describe
andit
.- The first test (
"should return name"
) mockssomeOtherFunction
to returntrue
(usingmockReturnValue
). It then callstestVitest
and asserts the returned value is "My Name is XYZ" usingexpect
. - The second test (
"should return age"
) mockssomeOtherFunction
to returnfalse
. It then callstestVitest
and 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 forspyOn
would be to unit test how many times the function has been executed)use case of
hoisting+mocking
:- for general test cases
Top comments (0)