js: examples of mocking with Jest

This commit is contained in:
thomasabishop 2023-09-26 20:25:41 +01:00
parent 5f0b68ccf2
commit c4e20ae208

View file

@ -8,12 +8,292 @@ tags: [javascript, testing]
## Mock a function
## Mock a class/module
```js
function sumOfFirstTenNumbers() {
let sum = 0;
for (let i = 1; i <= 10; i++) {
sum += i;
}
return sum;
}
```
```js
test("mock sumOfFirstTenNumbers function", () => {
const mockFunction = jest.fn().mockReturnValue(100);
const result = mockFunction();
expect(result).toBe(100);
expect(mockFunction).toHaveBeenCalled();
});
```
## Mocking classes/modules
### Classes
Let' say we have this class:
```js
// database.js
class Database {
connect() {}
save(data) {}
}
```
Then to mock:
```js
import Database from "./database";
// This will mock the whole Database class, replacing all methods with jest mock functions.
jest.mock("./database");
test("should use mocked save method", () => {
const dbInstance = new Database();
// Mocking the save method with a specific return value
dbInstance.save.mockReturnValue(true);
const result = dbInstance.save({ key: "value" });
expect(result).toBe(true);
expect(dbInstance.save).toHaveBeenCalledWith({ key: "value" });
// The connect method is still a mock function (but without a specific behavior).
dbInstance.connect();
expect(dbInstance.connect).toHaveBeenCalled();
});
```
### Modules
Say we have the following module file:
```js
// utils.js
export const doSomething = () => {
// ...
};
export const fetchUserData = async (userId) => {
const response = await axios.get(`/api/users/${userId}`);
return response.data;
};
```
Mocked:
```js
jest.mock("./utils", () => {
return {
doSomething: jest.fn(() => "mocked doSomething"),
fetchUserData: jest.fn((userId) =>
Promise.resolve({ id: userId, name: "Mock User" })
),
};
});
test("should use mocked module functions", () => {
expect(utils.doSomething()).toBe("mocked doSomething");
expect(utils.doSomething).toHaveBeenCalled();
const result = await utils.fetchUserData(123);
expect(result).toEqual({ id: 123, name: "Mock User" });
expect(utils.fetchUserData).toHaveBeenCalledWith(123);
});
```
## Check that a function has been called within another function
## Mock a function that needs to be called within another function
```js
function toBeCalledFunction() {
console.log("Original function called");
}
function callerFunction() {
toBeCalledFunction();
}
```
```js
test("spy on toBeCalledFunction", () => {
const spy = jest.spyOn(global, "toBeCalledFunction"); // Replace `global` with the appropriate object/context if the function is not global
callerFunction();
expect(spy).toHaveBeenCalled();
spy.mockRestore(); // Restore the original function after spying
});
```
## Mock a function that needs to resolve to something within another function
We have two functions, one that gets data and another that processes it. We want to mock the function that gets data and return a value that the processing function can use.
```js
async function getData() {
// ... Fetch some data from an API or database
return fetchedData;
}
async function processData() {
const data = await getData();
// ... Process the data
return processedData;
}
```
The mocking part:
```js
const mockData = { key: "value" }; // Mocked data
jest.mock("./path-to-file-where-getData-is", () => ({
getData: jest.fn().mockResolvedValue(mockData),
}));
test("test processData function", async () => {
const result = await processData();
// Now, result contains the processed version of mockData
expect(result).toEqual(/* expected processed data based on mockData */);
});
```
We could also combine the above with a spy to check that the `getData` function was called:
```js
const getDataSpy = jest
.spyOn(moduleContainingGetData, "getData")
.mockResolvedValue(mockData);
const result = await processData();
expect(getDataSpy).toHaveBeenCalled();
expect(result).toEqual(/* expected processed data based on mockData */);
getDataSpy.mockRestore();
```
## Mock a function that takes arguments
```js
function addPrefix(str) {
return `prefix-${str}`;
}
```
```js
test("dynamic mock for addPrefix function", () => {
const mockFunction = jest.fn((str) => `mock-${str}`);
// Example usage of mockFunction
const result1 = mockFunction("test");
const result2 = mockFunction("example");
expect(result1).toBe("mock-test");
expect(result2).toBe("mock-example");
});
```
## Mocking network requests
### Mocking Axios
```js
jest.mock("axios", () => ({
get: jest.fn().mockResolvedValue(mockData),
post: jest.fn().mockResolvedValue(mockData),
}));
```
Or we could implement this way:
```js
jest.mock("axios");
axios.get.mockResolvedValue({ data: "mockedData" });
axios.post.mockResolvedValue({ data: "mockedData" });
```
Then we can use the mocked axios functions in our tests:
```js
const result = await fetchData(); // the function that uses Axios `get``
expect(result).toBe("mockedGetData");
const result = await sendData({ key: "value" }); // the function tha uses Axios `post`
expect(result).toBe("mockedPostData");
```
### `mockImplementation`
For more configurable cases we can use `mockImplementation`:
```js
it("sends data", async () => {
// Mock axios.post using mockImplementation
axios.post.mockImplementation((url, data) => {
if (data.key === "value") {
return Promise.resolve({ data: "mockedPostData" });
} else {
return Promise.reject({ error: "An error occurred" });
}
});
const result = await sendData({ key: "value" });
expect(result).toBe("mockedPostData");
});
```
If we want to change the `get` and `post` values in different tests, we can do so by using `mockImplementation`:
## Mocking exceptions
Again we use `mockImplementation`:
Say we have the following function:
```js
// fetchData.js
import axios from "axios";
const fetchData = async (url) => {
try {
const response = await axios.get(url);
return response.data;
} catch (error) {
throw new Error("Error fetching data");
}
};
export default fetchData;
```
We would mock the success and the error as follows:
```js
import axios from "axios";
import fetchData from "./fetchData";
jest.mock("axios");
describe("fetchData", () => {
it("fetches data successfully", async () => {
axios.get.mockResolvedValue({ data: "mockedData" });
const result = await fetchData("https://api.example.com/data");
expect(result).toBe("mockedData");
});
it("throws an error when fetching fails", async () => {
axios.get.mockImplementation(() => {
throw new Error("API error");
});
// We use an asynchronous assertion here because we're expecting a promise to reject
await expect(fetchData("https://api.example.com/data")).rejects.toThrow(
"Error fetching data"
);
});
});
```