js: examples of mocking with Jest
This commit is contained in:
parent
5f0b68ccf2
commit
c4e20ae208
1 changed files with 282 additions and 2 deletions
|
@ -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"
|
||||
);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
|
Loading…
Add table
Reference in a new issue