We mostly need to communicate with servers to fetch or send data when developing web applications. The standard way to make these requests is using the Fetch API.
A good practice is placing these API calls in methods inside service files. Because then the logic is isolated and more convenient to test.
In this tutorial, we will write a test suit for an example Todos
service in Vitest by mocking the Fetch API.
You can find the source code on Github.
The Todos Service
Let’s imagine we have this service file that exports two methods, One that makes a GET
request to fetch a list of todos and returns the parsed JSON response. And, another one that makes a POST
request to create a new Todo and returns the parsed JSON response as well.
const BASE_URL = 'https://imaginary-todos-api.com/api/v1/todos'
export async function fetchTodoList({ token }) {
return (
await fetch(BASE_URL, {
headers: {
Authorization: `Bearer ${token}`,
},
})
).json()
}
export async function createTodo({ token, todo }) {
return (
await fetch(BASE_URL, {
method: 'POST',
body: JSON.stringify(todo),
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
},
})
).json()
}
They both send a JWT token for Authorization.
Firstly, we want to assert that fetchTodos
makes a GET request to the right endpoint with the right headers and returns the data. Secondly, we assert that the addTodo
method makes a POST request and sends an object as a payload and headers also.
How to Mock GET Requests in Vitest
Import the methods inside your test file. Afterward, mock the Fetch API using vi.fn()
.
Since we are not in a browser environment, we have to attach the mocked fetch method to the global
object.
import { describe, test, expect, vi } from 'vitest'
import { createTodo, fetchTodoList } from './todo.service'
global.fetch = vi.fn()
describe('Todo Service', () => {
test.todo('makes a GET request to fetch todo list')
test.todo('makes a POST request to create a todo')
})
Next, create a helper method that will return a similar response structure returned by fetch. We’re only interested in getting an object with a json
method. the json
method should return a promise that resolves with the data.
import { describe, test, expect, vi } from 'vitest'
import { createTodo, fetchTodoList } from './todo.service'
global.fetch = vi.fn()
function createFetchResponse(data) {
return { json: () => new Promise((resolve) => resolve(data)) }
}
Finally, implement the first test. the goal is to assert that the method fetchTodoList
calls the fetch method with the correct endpoint and headers and returns the correct data.
test('makes a GET request to fetch todo list and returns the result', async () => {
const todoListResponse = [
{
title: 'Unit test',
done: false,
},
]
const token = 'token'
fetch.mockResolvedValue(createFetchResponse(todoListResponse))
const todoList = await fetchTodoList({ token })
expect(fetch).toHaveBeenCalledWith(
'https://imaginary-todos-api.com/api/v1/todos',
{
headers: {
Authorization: `Bearer ${token}`,
},
},
)
expect(todoList).toStrictEqual(todoListResponse)
})
We use toHaveBeenCalledWith
to assert that we called the fetch method with the correct parameters. Then, we assert that the returned result from the method is the one returned by our API.
How to Mock Post Requests in Vitest
Moving on, implement the unit test for the createTodo
method. We should assert that the fetch method is called with the correct endpoint, HTTP method, headers, and payload. Furthermore, we check that the return value is the one returned by the json
method.
test('makes a POST request to create a todo', async () => {
const token = 'token'
const todo = {}
const response = {
id: 'random-id',
...todo,
}
fetch.mockResolvedValue(createFetchResponse(response))
const newTodo = await createTodo({
token,
todo,
})
expect(fetch).toHaveBeenCalledWith(
'https://imaginary-todos-api.com/api/v1/todos',
{
method: 'POST',
body: JSON.stringify(todo),
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
},
},
)
expect(newTodo).toStrictEqual(response)
})
In a similar fashion to the GET test, we use toHaveBeenCalledWith
but with different expected parameters.
The result returned by the .json
method will have an id
, so we expect the returned value of the method to match it.
In the second unit test, the fetch method still has a call history, we need to reset that. In a beforeEach hook, call the mockReset method on the fetch mock.
import { describe, test, expect, vi, beforeEach } from 'vitest'
import { createTodo, fetchTodoList } from './todo.service'
//... code
describe('Todo Service', () => {
beforeEach(() => {
global.fetch.mockReset()
})
//... tests
If we run the tests we should see a similar result to this:
Conclusion
In this tutorial, we learned how to mock the fetch API in Vitest. As you have seen it’s a simple process and helps with asserting that we’re doing the right thing. I hope that you have learned something valuable, cheers!