Warning
This package is deprecated. Use MSW instead as it promotes good mocking patterns.
Easiest way to mock fetch
Compatible with Vitest and Jest when using ESM flag or with transform for /node_modules/vi-fetch
.
# with npm
npm install -D vi-fetch
# with pnpm
pnpm install -D vi-fetch
# with yarn
yarn install -D vi-fetch
Before using mock API, you need to set up fetch mock. To do this, import vi-fetch/setup
in your setup file:
import 'vi-fetch/setup';
If fetch
is located on another object, you can manually setup it with prepareFetch
function, imported from vi-fetch
:
import { prepareFetch } from 'vi-fetch';
prepareFetch(globalThis, 'fetchNode');
Add vi-fetch/matchers
to your types
config in tsconfig.json
, if you are using TypeScript:
{
"compilerOptions": {
"types": ["vi-fetch/matchers"]
}
}
Also, it is recommended to clear up all mocks before each test to avoid collision between tests:
import { mockFetch } from 'vi-fetch';
beforeEach(() => {
mockFetch.clearAll();
});
Calling fetch
in browser can resolve in multiple situations:
- resolve with
ok: true
- resolve with
ok: false
- throw
TypeError
if something is wrong
Usually we know the API endpoint and need to test only these three situations, and vi-fetch
provides a wrapper around fetch
to make it easy.
Use mockFetch
function to define fetch
behavior. Aliases for popular methods are available: mockGet
, mockPost
, mockPut
, mockPatch
, mockDelete
.
You can ignore query string when mocking, if you provide last argument as
false
. By default, query string is necessary, if your fetch call has it.
To mock fetch
with ok: true
, you can use willResolve/willResolveOnce
methods.
- By default,
willResolve
will resolve to{}
, if no body is specified. - By default,
willResolve
will returnstatus: 200
. You can override it with second argument.
import { test, expect } from 'vitest';
import { mockGet } from 'vi-fetch';
import { renderApplesTable } from '../src/ApplesTable';
test('apples endpoint was called', async () => {
// or just "/apples" if you configure baseUrl
const mock = mockGet('https://api.com/v1/apples').willResolve([
{ count: 33 },
]);
await renderApplesTable();
expect(mock).toHaveFetched();
});
To mock fetch
with ok: false
, you can use willFail/willFailOnce
methods.
- By default,
willFail
will resolve to{}
, if no body is specified. - By default,
willFail
will returnstatus: 500
andstatusText: Internal Server Error
. You can override it with second and third arguments. If you don't specifystatus
, it will guess thestatusText
fromstatus
.
import { test, expect } from 'vitest';
import { mockGet } from 'vi-fetch';
import { renderApplesTable } from '../src/ApplesTable';
test('apples endpoint was called', async () => {
// or just "/apples" if you configure baseUrl
const mock = mockGet('https://api.com/v1/apples').willFail(
{ error: 'no apples' },
404
);
await renderApplesTable();
expect(mock).toHaveFetched();
});
Warning: calling
willFail
will override every other mock.
If you have logic that depends on fetch
throwing, you can test it with willThrow/willThrowOnce
methods.
willThrow
requires Error
object or a message. If message
is specified, fetch
will throw TypeError
with this message.
import { test, expect } from 'vitest';
import { mockGet } from 'vi-fetch';
import { renderApplesTable } from '../src/ApplesTable';
test('apples endpoint was called', async () => {
// or just "/apples" if you configure baseUrl
const mock = mockGet('https://api.com/v1/apples').willThrow('cors error');
await renderApplesTable();
expect(mock).toHaveFetched();
});
Warning: calling
willThrow
will override every other mock.
If you want to make custom behaviour when the fetch
is invoking, you can use willDo
method. The first argument is URL
instance, the rest are fetch
arguments.
import { test, expect } from 'vitest';
import { mockGet } from 'vi-fetch';
import { renderApplesTable } from '../src/ApplesTable';
test('apples endpoint was called', async () => {
// or just "/apples" if you configure baseUrl
const mock = mockGet('https://api.com/v1/apples').willDo((url) => {
if (url.searchParams.get('offset') > 2) {
return { body: [] };
}
return { body: [{ count: 3 }] };
});
await renderApplesTable();
expect(mock).toHaveFetched();
});
Warning: calling
willDo
will override every other mock.
You can clear all implementation details with clear
method.
import { test, expect } from 'vitest';
import { mockGet } from 'vi-fetch';
import { renderApplesTable } from '../src/ApplesTable';
test('apples endpoint was called', async () => {
// or just "/apples" if you configure baseUrl
const mock = mockGet('https://api.com/v1/apples').willResolve([
{ count: 33 },
]);
await renderApplesTable();
expect(mock).toHaveFetched();
mock.clear();
expect(mock).not.toHaveFetched();
});
This method lets you manipulate Response
headers, if you depend on them. All responses of the mock will return these headers. If you don't specify Content-Type
header, mockFetch
tries to guess it from the content you provided as a response.
import { test, expect } from 'vitest';
import { mockGet } from 'vi-fetch';
test('apples endpoint was called', async () => {
// or just "/apples" if you configure baseUrl
const mock = mockGet('https://api.com/v1/apples')
.withHeaders([['Content-Type', 'text/plain']])
.willResolve([{ count: 33 }]);
await renderApplesTable();
const response = mock.getRouteResults()[0];
expect(response.headers.get('Content-Type')).toBe('text/plain');
});
To not repeat baseUrl
every time you mock endpoint, you can configure it globally:
import { mockFetch } from 'vi-fetch';
mockFetch.setOptions({
baseUrl: 'https://api.com/v1',
});
You can also create isolated mockFetch
with its own options to not collide with globals. It also returns aliased methods.
import { createMockApi } from 'vi-fetch';
import { test, expect } from 'vitest';
const { mockFetch } = createMockApi({ baseUrl: 'https://api.com/v2' });
test('isolated', async () => {
const mock = mockFetch('GET', '/apples').willResolve(33); // or mockGet
await fetch('http://api.com/v2/apples');
expect(mock).toHaveFetched();
});
You can ignore queryString
to make every fetch
call, that starts with url, to go through this mock by passing false
as the last argument, and then check it with toHaveFetchedWithQuery
:
import { test, expect } from 'vitest';
import { mockGet } from 'vi-fetch';
test('apples endpoint was called', async () => {
const mock = mockGet('https://api.com/v1/apples', false).willResolve([
{ count: 33 },
]);
await fetch('https://api.com/v1/apples?count=5&offset=2');
expect(mock).toHaveFetched();
expect(mock).toHaveFetchedWithQuery({ count: 5, offset: 2 });
});
You can also use regular expressions for urls:
import { test, expect } from 'vitest';
import { mockGet } from 'vi-fetch';
test('apples endpoint was called', async () => {
const mock = mockGet(/\/apples/).willResolve([{ count: 33 }]);
await fetch('https://api.com/v1/apples');
expect(mock).toHaveFetched();
});
Imagine we have a test with this setup:
import { expect, test } from 'vitest';
import { mockFetch } from 'vi-fetch';
mockFetch.setOptions({ baseUrl: 'https://api.com/v1' });
// usually you would call fetch inside your source code
const callFetch = (url, options) => {
return fetch('https://api.com/v1' + url, options);
};
If you want to check if fetch
was called with appropriate URL and method at least once, you can use toHaveFetched
. If you want to be more sure about returned response, you can pass it as optional argument. It will pass if any call returned this response.
test('api was called', async () => {
const mock = mockFetch('GET', '/apples').willResolve({
count: 0,
apples: [],
});
await callFetch('/apples');
expect(mock).toHaveFetched();
expect(mock).toHaveFetched({
count: 0,
apples: [],
});
});
If you want to check the returned value of nth call, you can use toHaveFetchedNthTime
(index starts at 1).
test('api was called', async () => {
const mock = mockFetch('GET', '/apples').willResolve({
count: 0,
apples: [],
});
await callFetch('/apples');
mock.willResolve({
count: 1,
apples: ['2kg'],
});
await callFetch('/apples');
expect(mock).toHaveFetchedNthTime(2, {
count: 1,
apples: ['2kg'],
});
});
If you need to check if URL was called multiple times, you can use toHaveFetchedTimes
.
test('api was called 3 times', async () => {
const mock = mockFetch('GET', '/apples', false).willResolve({
count: 0,
apples: [],
});
await callFetch('/apples');
await callFetch('/apples?offset=2');
await callFetch('/apples?offset=3');
expect(mock).toHaveFetchedTimes(3);
});
If you need to check if URL was called with the specific body, you can use toHaveFetchedWithBody/toHaveFetchedNthTimeWithBody
.
test('api was called with json', async () => {
const mock = mockFetch('POST', '/apples').willResolve({
count: 0,
apples: [],
});
await callFetch('/apples', {
method: 'POST',
body: '{ "foo": "baz" }',
});
expect(mock).toHaveFetchedWithBody({ foo: 'baz' });
expect(mock).toHaveFetchedWithBody('{ "foo": "baz" }');
expect(mock).toHaveFetchedNthTimeWithBody(1, { foo: 'baz' });
});
Supports string, object, Blob, ArrayBuffer, and FormData. Will try to guess
Content-Type
header if not specified.
If you need to check if URL was called with the specific query string, you can use toHaveFetchedWithQuery/toHaveFetchedNthTimeWithQuery
.
Uses query-string parse function with default options to parse query.
test('api was called with query', async () => {
const mock = mockFetch('GET', '/apples').willResolve({
count: 0,
apples: [],
});
await callFetch('/apples?count=5&offset=2');
expect(mock).toHaveFetchedWithQuery({ count: 5, offset: 2 });
expect(mock).toHaveFetchedWithQuery(
new URLSearchParams({ count: '5', offset: '2' })
);
expect(mock).toHaveFetchedWithQuery('count=5&offset=2');
expect(mock).toHaveFetchedNthTimeWithQuery(1, { count: 5, offset: 2 });
expect(mock).toHaveFetchedNthTimeWithQuery(1, 'count=5&offset=2');
});
Supports string, object, and URLSearchParams