How to Mock Fetch API in Vitest

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:

vitest mock fetch API successful tests

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!

Amenallah Hsoumi
Amenallah Hsoumi

Senior Software Engineer, Indie Hacker, building cool stuff on the web!

Articles: 19