When writing unit tests for services that utilize Axios as an HTTP client library. We need to mock it it’s not a good practice to do real requests in that environment. so How do we mock Axios?
In a nutshell, we have to mock the methods we use in axios, like .post and .get to return a promise that resolves with the data we specify using mockResolvedValue
.
Let’s write some unit tests for an example service that uses Axios.
Example Users Service
As an example let’s imagine we have this service file that fetches and creates users.
import axios from 'axios'
const BASE_URL = 'https://jsonplaceholder.typicode.com'
export const fetchUsers = async () => {
return (await axios.get(`${BASE_URL}/users`)).data
}
export const createUser = async (user) => {
return (await axios.post(`${BASE_URL}/users`, user)).data
}
The fetchUsers
function makes a GET request to fetch users. And the createUser
function makes a POST request to create a new user. Both functions return the data from the response. Let’s develop unit tests for both of them.
Mock GET Requests
First, implement the unit test for the fetchUsers
method. We expect that it calls axios.get
with the proper URL and returns the data returned by it.
To get started, mock axios
itself. Then, inside the test body mock the get method to return a promise with mock data.
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { createUser, fetchUsers } from './users.service'
import axios from 'axios'
vi.mock('axios')
describe('Users Service', () => {
describe('fetchUsers', () => {
test('makes a GET request to fetch users', async () => {
const usersMock = [{ id: 1 }, { id: 2 }]
axios.get.mockResolvedValue({
data: usersMock,
})
})
})
})
Now if we call axios.get
within our test, it will return a promise that resolves with the data. just as it would in our service file.
Afterward, call the function fetchUsers
and expect that axios.get
has been called with the proper URL:
describe('fetchUsers', () => {
test('makes a GET request to fetch users', async () => {
const usersMock = [{ id: 1 }, { id: 2 }]
axios.get.mockResolvedValue({
data: usersMock,
})
const users = await fetchUsers()
expect(axios.get).toHaveBeenCalledWith('https://jsonplaceholder.typicode.com/users')
})
})
Next, expect that the users
variable is strictly equal to the usersMock we provided earlier to mockResolvedValue
:
describe('fetchUsers', () => {
test('makes a GET request to fetch users', async () => {
const usersMock = [{ id: 1 }, { id: 2 }]
axios.get.mockResolvedValue({
data: usersMock,
})
const users = await fetchUsers()
expect(axios.get).toHaveBeenCalledWith('https://jsonplaceholder.typicode.com/users')
expect(users).toStrictEqual(usersMock)
})
})
Finally, reset the mocked methods so that we have an empty call history before each test. This will help us evade false positives, where the method has been called in an earlier test while we expect it to be called in a current one:
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { createUser, fetchUsers } from './users.service'
import axios from 'axios'
vi.mock('axios')
describe('Users Service', () => {
beforeEach(() => {
axios.get.mockReset()
})
describe('fetchUsers', () => {
test('makes a GET request to fetch users', async () => {
const usersMock = [{ id: 1 }, { id: 2 }]
axios.get.mockResolvedValue({
data: usersMock,
})
const users = await fetchUsers()
expect(axios.get).toHaveBeenCalledWith('https://jsonplaceholder.typicode.com/users')
expect(users).toStrictEqual(usersMock)
})
})
})
If run the tests we should see that it passes:
Mock POST Requests
Let’s now write the test for createUser. We expect it to call axios.post
with a user payload and return the data from the response.
First, create the test case and mock the post method:
describe('createUser', () => {
test('makes a POST request to create a new user', async () => {
const newUserPayload = {
name: 'john doe',
}
const newUserMock = {
id: 1,
...newUserPayload,
}
axios.post.mockResolvedValue({
data: newUserMock,
})
const newUser = await createUser(newUserPayload)
})
})
Similar to what we did earlier, we use mockResolvedValue
to return a response with a data attribute as the Axios do in the real implementation.
Next, add the expectations. Assert that the post method has been called and that the newUser object is strictly equal to the one we return from the mock
describe('createUser', () => {
test('makes a POST request to create a new user', async () => {
const newUserPayload = {
name: 'john doe',
}
const newUserMock = {
id: 1,
...newUserPayload,
}
axios.post.mockResolvedValue({
data: newUserMock,
})
const newUser = await createUser(newUserPayload)
expect(axios.post).toHaveBeenCalledWith('https://jsonplaceholder.typicode.com/users', newUserPayload)
expect(newUser).toStrictEqual(newUserMock)
})
})
Finally, reset the mocked post method also in the beforEach
block and run the tests should be passing
beforeEach(() => {
axios.get.mockReset()
axios.post.mockReset()
})
Conclusion
In this tutorial we learned how to mock axios with Vitest, I hope that helps you in your unit-testing journey.
This tutorial is part of a series that goes through mocking the commonly used Web APIs and Packages, you can find the others here. Also, you can find the code for this tutorial on Github. Cheers!