Gathering detailed insights and metrics for vitest-fetch-mock
Gathering detailed insights and metrics for vitest-fetch-mock
Gathering detailed insights and metrics for vitest-fetch-mock
Gathering detailed insights and metrics for vitest-fetch-mock
npm install vitest-fetch-mock
53.2
Supply Chain
88.2
Quality
86.5
Maintenance
100
Vulnerability
97.9
License
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
68 Stars
252 Commits
9 Forks
2 Watching
2 Branches
41 Contributors
Updated on 25 Nov 2024
TypeScript (98.66%)
JavaScript (1.34%)
Cumulative downloads
Total Downloads
Last day
0.2%
29,458
Compared to previous day
Last week
4.9%
152,527
Compared to previous week
Last month
19.1%
628,526
Compared to previous month
Last year
107.7%
5,341,345
Compared to previous year
1
This project was forked from jest-fetch-mock, and tweaked slightly to run
with Vitest instead of Jest. It is mostly compatible with jest-fetch-mock
, with the main difference being that you
need to create fetchMock with a function call, and provide vi
to it, rather than relying on a global vi
or (jest
in jest-fetch-mock's case). See Usage for more details.
Fetch is the canonical way to do HTTP requests in the browser and other modern runtimes. Vitest Fetch Mock allows you to
easily mock your fetch
calls and return the response you need to fake the HTTP requests. It's easy to setup and you
don't need a library like nock
to get going and it uses Vitest's built-in support for mocking under the surface. This
means that any of the vi.fn()
methods are also available. For more information on the Vitest mock API, check their
docs here
As of version 0.4.0, vitest-fetch-mock
mocks the global
Fetch method, which is present in all modern runtimes
and browsers. Previous versions used a fetch polyfill to run in older versions of Node.js. See
Compatibility for details.
fetch.mockResponses
fetch.resetMocks
fetch.mock
to inspect the mock state of each fetch callThe most recent versions of vitest-fetch-mock support Vitest 2 and Node.js 18 and above. If you are using Vitest version
1.x or an older version of node, please install vitest-fetch-mock0.2.2
.
To setup your fetch mock you need to do the following things:
$ npm install --save-dev vitest-fetch-mock
Create a setupVitest
file to setup the mock or add this to an existing setupFile
. :
1//setupVitest.js or similar file 2import createFetchMock from 'vitest-fetch-mock'; 3import { vi } from 'vitest'; 4 5const fetchMocker = createFetchMock(vi); 6 7// sets globalThis.fetch and globalThis.fetchMock to our mocked version 8fetchMocker.enableMocks();
Add the setup file to your vitest config:
1"test": { 2 "setupFiles": [ 3 "./setupVitest.js" 4 ] 5}
With this done, you'll have fetch
and fetchMock
available on the global scope. Fetch will be used as usual by your
code and you'll use fetchMock
in your tests
If you would like to have the 'fetchMock' available in all tests but not enabled then add fetchMock.dontMock()
after
the ...enableMocks()
line in setupVitest.js
:
1import createFetchMock from 'vitest-fetch-mock'; 2import { vi } from 'vitest'; 3const fetchMocker = createFetchMock(vi); 4 5// adds the 'fetchMock' global variable and rewires 'fetch' global to call 'fetchMock' instead of the real implementation 6fetchMocker.enableMocks(); 7 8// changes default behavior of fetchMock to use the real 'fetch' implementation and not mock responses 9fetchMocker.dontMock();
If you want a single test file to return to the default behavior of mocking all responses, add the following to the test file:
1beforeEach(() => { 2 // if you have an existing `beforeEach` just add the following line to it 3 fetchMocker.doMock(); 4});
To enable mocking for a specific URL only:
1beforeEach(() => { 2 // if you have an existing `beforeEach` just add the following lines to it 3 fetchMocker.mockIf(/^https?:\/\/example.com.*$/, (req) => { 4 if (req.url.endsWith('/path1')) { 5 return 'some response body'; 6 } else if (req.url.endsWith('/path2')) { 7 return { 8 body: 'another response body', 9 headers: { 10 'X-Some-Response-Header': 'Some header value', 11 }, 12 }; 13 } else { 14 return { 15 status: 404, 16 body: 'Not Found', 17 }; 18 } 19 }); 20});
If you have changed the default behavior to use the real implementation, you can guarantee the next call to fetch will
be mocked by using the mockOnce
function:
1fetchMocker.mockOnce('the next call to fetch will always return this as the body');
This function behaves exactly like fetchMocker.once
but guarantees the next call to fetch
will be mocked even if the
default behavior of fetchMock is to use the real implementation. You can safely convert all you fetchMocker.once
calls
to fetchMocker.mockOnce
without a risk of changing their behavior.
Add the following line to the start of your test case (before any other imports)
1import createFetchMock from 'vitest-fetch-mock'; 2import { vi } from 'vitest'; 3 4const fetchMocker = createFetchMock(vi); 5fetchMocker.enableMocks();
If you are using TypeScript and receive errors about the fetchMock
global not existing, add a global.d.ts
file to
the root of your project (or add the following line to an existing global file):
1import 'vitest-fetch-mock';
fetch.mockResponse(bodyOrFunction, init): fetch
- Mock all fetch callsfetch.mockResponseOnce(bodyOrFunction, init): fetch
- Mock each fetch call independentlyfetch.once(bodyOrFunction, init): fetch
- Alias for mockResponseOnce(bodyOrFunction, init)
fetch.mockResponses(...responses): fetch
- Mock multiple fetch calls independently
[bodyOrFunction, init]
fetch.mockReject(errorOrFunction): fetch
- Mock all fetch calls, letting them fail directlyfetch.mockRejectOnce(errorOrFunction): fetch
- Let the next fetch call fail directlyfetch.mockAbort(): fetch
- Causes all fetch calls to reject with an "Aborted!" errorfetch.mockAbortOnce(): fetch
- Causes the next fetch call to reject with an "Aborted!" errorInstead of passing body, it is also possible to pass a function that returns a promise. The promise should resolve with a string or an object containing body and init props
i.e:
1fetch.mockResponse(() => callMyApi().then((res) => ({ body: 'ok' }))); 2// OR 3fetch.mockResponse(() => callMyApi().then((res) => 'ok'));
The function may take an optional "request" parameter of type http.Request
:
1fetch.mockResponse((req) => 2 req.url === 'http://myapi/' ? callMyApi().then((res) => 'ok') : Promise.reject(new Error('bad url')) 3);
Note: the request "url" is parsed and then printed using the equivalent of new URL(input).href
so it may not match
exactly with the URL's passed to fetch
if they are not fully qualified. For example, passing "http://foo.com" to
fetch
will result in the request URL being "http://foo.com/" (note the trailing slash).
The same goes for rejects:
1fetch.mockReject(() => doMyAsyncJob().then((res) => Promise.reject(res.errorToRaise))); 2// OR 3fetch.mockReject((req) => 4 req.headers.get('content-type') === 'text/plain' 5 ? Promise.reject('invalid content type') 6 : doMyAsyncJob().then((res) => Promise.reject(res.errorToRaise)) 7);
fetch.resetMocks()
- Clear previously set mocks so they do not bleed into other mocksfetch.enableMocks()
- Enable fetch mocking by overriding global.fetch
and mocking node-fetch
fetch.disableMocks()
- Disable fetch mocking and restore default implementation of fetch
and/or node-fetch
fetch.mock
- The mock state for your fetch calls. Make assertions on the arguments given to fetch
when called by
the functions you are testing. For more information check the
vitest docsFor information on the arguments body and init can take, you can look at the MDN docs on the Response Constructor
function, which vitest-fetch-mock
uses under the surface.
https://developer.mozilla.org/en-US/docs/Web/API/Response/Response
Each mocked response or err or will return a MockInstance Function. You can use
methods like .toHaveBeenCalledWith
to ensure that the mock function was called with specific arguments. For more
methods detail, take a look at this.
In most of the complicated examples below, I am testing my action creators in Redux, but it doesn't have to be used with Redux.
In this simple example I won't be using any libraries. It is a simple fetch request, in this case to google.com. First
we setup the beforeEach
callback to reset our mocks. This isn't strictly necessary in this example, but since we will
probably be mocking fetch more than once, we need to reset it across our tests to assert on the arguments given to
fetch. Make sure the function wrapping your test is marked as async.
Once we've done that we can start to mock our response. We want to give it an object with a data
property and a string
value of 12345
and wrap it in JSON.stringify
to JSONify it. Here we use mockResponseOnce
, but we could also use
once
, which is an alias for a call to mockResponseOnce
.
We then call the function that we want to test with the arguments we want to test with. We use await
to wait until the
response resolves, and then assert we have got the correct data back.
Finally we can assert on the .mock
state that Vitest provides for us to test what arguments were given to fetch and
how many times it was called
1//api.js 2export function APIRequest(who) { 3 if (who === 'google') { 4 return fetch('https://google.com').then((res) => res.json()); 5 } else { 6 return 'no argument provided'; 7 } 8}
1//api.test.js 2import { APIRequest } from './api'; 3 4describe('testing api', () => { 5 beforeEach(() => { 6 fetch.resetMocks(); 7 }); 8 9 it('calls google and returns data to me', async () => { 10 fetch.mockResponseOnce(JSON.stringify({ data: '12345' })); 11 12 //assert on the response 13 const res = await APIRequest('google'); 14 expect(res.data).toEqual('12345'); 15 16 //assert on the times called and arguments given to fetch 17 expect(fetch.requests().length).toEqual(1); 18 expect(fetch.requests()[0].url).toEqual('https://google.com/'); 19 }); 20});
In this example I am mocking just one fetch call. Any additional fetch calls in the same function will also have the same mock response. For more complicated functions with multiple fetch calls, you can check out example 3.
1import configureMockStore from 'redux-mock-store'; // mock store 2import thunk from 'redux-thunk'; 3 4const middlewares = [thunk]; 5const mockStore = configureMockStore(middlewares); 6 7import { getAccessToken } from './accessToken'; 8 9describe('Access token action creators', () => { 10 it('dispatches the correct actions on successful fetch request', () => { 11 fetch.mockResponse(JSON.stringify({ access_token: '12345' })); 12 13 const expectedActions = [{ type: 'SET_ACCESS_TOKEN', token: { access_token: '12345' } }]; 14 const store = mockStore({ config: { token: '' } }); 15 16 return ( 17 store 18 .dispatch(getAccessToken()) 19 //getAccessToken contains the fetch call 20 .then(() => { 21 // return of async actions 22 expect(store.getActions()).toEqual(expectedActions); 23 }) 24 ); 25 }); 26});
In this example I am mocking just one fetch call but this time using the mockReject
function to simulate a failed
request. Any additional fetch calls in the same function will also have the same mock response. For more complicated
functions with multiple fetch calls, you can check out example 3.
1import configureMockStore from 'redux-mock-store'; // mock store 2import thunk from 'redux-thunk'; 3 4const middlewares = [thunk]; 5const mockStore = configureMockStore(middlewares); 6 7import { getAccessToken } from './accessToken'; 8 9describe('Access token action creators', () => { 10 it('dispatches the correct actions on a failed fetch request', () => { 11 fetch.mockReject(new Error('fake error message')); 12 13 const expectedActions = [{ type: 'SET_ACCESS_TOKEN_FAILED', error: { status: 503 } }]; 14 const store = mockStore({ config: { token: '' } }); 15 16 return ( 17 store 18 .dispatch(getAccessToken()) 19 //getAccessToken contains the fetch call 20 .then(() => { 21 // return of async actions 22 expect(store.getActions()).toEqual(expectedActions); 23 }) 24 ); 25 }); 26});
Fetches can be mocked to act as if they were aborted during the request. This can be done in 4 ways:
1describe('Mocking aborts', () => { 2 beforeEach(() => { 3 fetch.resetMocks(); 4 fetch.doMock(); 5 vi.useFakeTimers(); 6 }); 7 afterEach(() => { 8 vi.useRealTimers(); 9 }); 10 11 it('rejects with an Aborted! Error', () => { 12 fetch.mockAbort(); 13 expect(fetch('/')).rejects.toThrow('Aborted!'); 14 }); 15 it('rejects with an Aborted! Error once then mocks with empty response', async () => { 16 fetch.mockAbortOnce(); 17 await expect(fetch('/')).rejects.toThrow('Aborted!'); 18 await expect(request()).resolves.toEqual(''); 19 }); 20 21 it('throws when passed an already aborted abort signal', () => { 22 const c = new AbortController(); 23 c.abort(); 24 expect(() => fetch('/', { signal: c.signal })).toThrow('Aborted!'); 25 }); 26 27 it('rejects when aborted before resolved', async () => { 28 const c = new AbortController(); 29 fetch.mockResponse(async () => { 30 vi.advanceTimersByTime(60); 31 return ''; 32 }); 33 setTimeout(() => c.abort(), 50); 34 await expect(fetch('/', { signal: c.signal })).rejects.toThrow('Aborted!'); 35 }); 36});
Set the counter option >= 1 in the response init object to mock a redirected response https://developer.mozilla.org/en-US/docs/Web/API/Response/redirected. Note, this will only work in Node.js as it's a feature of node fetch's response class https://github.com/node-fetch/node-fetch/blob/master/src/response.js#L39.
1fetchMock.mockResponse('<main></main>', {
2 counter: 1,
3 status: 200,
4 statusText: 'ok',
5});
In this next example, the store does not yet have a token, so we make a request to get an access token first. This means
that we need to mock two different responses, one for each of the fetches. Here we can use fetch.mockResponseOnce
or
fetch.once
to mock the response only once and call it twice. Because we return the mocked function, we can chain this
jQuery style. It internally uses Vitest's mockImplementationOnce
. You can read more about it on the
Vitest documentation
1import configureMockStore from 'redux-mock-store'; 2import thunk from 'redux-thunk'; 3 4const middlewares = [thunk]; 5const mockStore = configureMockStore(middlewares); 6 7import { getAnimeDetails } from './animeDetails'; 8 9describe('Anime details action creators', () => { 10 it('dispatches requests for an access token before requesting for animeDetails', () => { 11 fetch.once(JSON.stringify({ access_token: '12345' })).once(JSON.stringify({ name: 'naruto' })); 12 13 const expectedActions = [ 14 { type: 'SET_ACCESS_TOKEN', token: { access_token: '12345' } }, 15 { type: 'SET_ANIME_DETAILS', animeDetails: { name: 'naruto' } }, 16 ]; 17 const store = mockStore({ config: { token: null } }); 18 19 return ( 20 store 21 .dispatch(getAnimeDetails('21049')) 22 //getAnimeDetails contains the 2 fetch calls 23 .then(() => { 24 // return of async actions 25 expect(store.getActions()).toEqual(expectedActions); 26 }) 27 ); 28 }); 29});
fetch.mockResponses
fetch.mockResponses
takes as many arguments as you give it, all of which are arrays representing each Response Object.
It will then call the mockImplementationOnce
for each response object you give it. This reduces the amount of
boilerplate code you need to write. An alternative is to use .once
and chain it multiple times if you don't like
wrapping each response arguments in a tuple/array.
In this example our actionCreator calls fetch
4 times, once for each season of the year and then concatenates the
results into one final array. You'd have to write fetch.mockResponseOnce
4 times to achieve the same thing:
1describe('getYear action creator', () => { 2 it('dispatches the correct actions on successful getSeason fetch request', () => { 3 fetch.mockResponses( 4 [JSON.stringify([{ name: 'naruto', average_score: 79 }]), { status: 200 }], 5 [JSON.stringify([{ name: 'bleach', average_score: 68 }]), { status: 200 }], 6 [JSON.stringify([{ name: 'one piece', average_score: 80 }]), { status: 200 }], 7 [JSON.stringify([{ name: 'shingeki', average_score: 91 }]), { status: 200 }] 8 ); 9 10 const expectedActions = [ 11 { 12 type: 'FETCH_ANIMELIST_REQUEST', 13 }, 14 { 15 type: 'SET_YEAR', 16 payload: { 17 animes: [ 18 { name: 'naruto', average_score: 7.9 }, 19 { name: 'bleach', average_score: 6.8 }, 20 { name: 'one piece', average_score: 8 }, 21 { name: 'shingeki', average_score: 9.1 }, 22 ], 23 year: 2016, 24 }, 25 }, 26 { 27 type: 'FETCH_ANIMELIST_COMPLETE', 28 }, 29 ]; 30 const store = mockStore({ 31 config: { 32 token: { access_token: 'wtw45CmyEuh4P621IDVxWkgVr5QwTg3wXEc4Z7Cv' }, 33 }, 34 years: [], 35 }); 36 37 return ( 38 store 39 .dispatch(getYear(2016)) 40 //This calls fetch 4 times, once for each season 41 .then(() => { 42 // return of async actions 43 expect(store.getActions()).toEqual(expectedActions); 44 }) 45 ); 46 }); 47});
fetch.resetMocks
fetch.resetMocks
resets the fetch
mock to give fresh mock data in between tests. It only resets the fetch
calls as
to not disturb any other mocked functionality.
1describe('getYear action creator', () => { 2 beforeEach(() => { 3 fetch.resetMocks(); 4 }); 5 it('dispatches the correct actions on successful getSeason fetch request', () => { 6 7 fetch.mockResponses( 8 [ 9 JSON.stringify([ {name: 'naruto', average_score: 79} ]), { status: 200} 10 ], 11 [ 12 JSON.stringify([ {name: 'bleach', average_score: 68} ]), { status: 200} 13 ] 14 ) 15 16 const expectedActions = [ 17 { 18 type: 'FETCH_ANIMELIST_REQUEST' 19 }, 20 { 21 type: 'SET_YEAR', 22 payload: { 23 animes: [ 24 {name: 'naruto', average_score: 7.9}, 25 {name: 'bleach', average_score: 6.8} 26 ], 27 year: 2016, 28 } 29 }, 30 { 31 type: 'FETCH_ANIMELIST_COMPLETE' 32 } 33 ] 34 const store = mockStore({ 35 config: { token: { access_token: 'wtw45CmyEuh4P621IDVxWkgVr5QwTg3wXEc4Z7Cv' }}, 36 years: [] 37 }) 38 39 return store.dispatch(getYear(2016)) 40 //This calls fetch 2 times, once for each season 41 .then(() => { // return of async actions 42 expect(store.getActions()).toEqual(expectedActions) 43 }) 44 }); 45 it('dispatches the correct actions on successful getSeason fetch request', () => { 46 47 fetch.mockResponses( 48 [ 49 JSON.stringify([ {name: 'bleach', average_score: 68} ]), { status: 200} 50 ], 51 [ 52 JSON.stringify([ {name: 'one piece', average_score: 80} ]), { status: 200} 53 ], 54 [ 55 JSON.stringify([ {name: 'shingeki', average_score: 91} ]), { status: 200} 56 ] 57 ) 58 59 const expectedActions = [ 60 { 61 type: 'FETCH_ANIMELIST_REQUEST' 62 }, 63 { 64 type: 'SET_YEAR', 65 payload: { 66 animes: [ 67 {name: 'bleach', average_score: 6.8}, 68 {name: 'one piece', average_score: 8}, 69 {name: 'shingeki', average_score: 9.1} 70 ], 71 year: 2016, 72 } 73 }, 74 { 75 type: 'FETCH_ANIMELIST_COMPLETE' 76 } 77 ] 78 const store = mockStore({ 79 config: { token: { access_token: 'wtw45CmyEuh4P621IDVxWkgVr5QwTg3wXEc4Z7Cv' }}, 80 years: [] 81 }) 82 83 return store.dispatch(getYear(2016)) 84 //This calls fetch 3 times, once for each season 85 .then(() => { // return of async actions 86 expect(store.getActions()).toEqual(expectedActions) 87 }) 88 , 89 90 }) 91})
fetch.mock
to inspect the mock state of each fetch callfetch.mock
by default uses Vitest's mocking functions. Therefore you can make
assertions on the mock state. In this example we have an arbitrary function that makes a different fetch request based
on the argument you pass to it. In our test, we run Vitest's beforeEach()
and make sure to reset our mock before each
it()
block as we will make assertions on the arguments we are passing to fetch()
. Then we use the fetch.requests()
function to give us a history of all non-aborted requests (normalized) that were made. It can give you information on
each call, and their arguments which you can use for your expect()
calls. Vitest also comes with some nice aliases for
the most used ones.
1// api.js 2 3export function APIRequest(who) { 4 if (who === 'facebook') { 5 return fetch('https://facebook.com'); 6 } else if (who === 'twitter') { 7 return fetch('https://twitter.com'); 8 } else { 9 return fetch('https://google.com'); 10 } 11}
1// api.test.js 2import { APIRequest } from './api'; 3 4describe('testing api', () => { 5 beforeEach(() => { 6 fetch.resetMocks(); 7 }); 8 9 it('calls google by default', () => { 10 fetch.mockResponse(JSON.stringify({ secret_data: '12345' })); 11 APIRequest(); 12 13 // there was one request, which was not aborted 14 expect(fetch.requests().length).toEqual(1); 15 expect(fetch.requests()[0].url).toEqual('https://google.com/'); 16 }); 17 18 it('calls facebook', () => { 19 fetch.mockResponse(JSON.stringify({ secret_data: '12345' })); 20 APIRequest('facebook'); 21 22 expect(fetch.requests().length).toEqual(2); 23 expect(fetch.requests()[0].url).toEqual('https://facebook.com/someOtherResource'); 24 expect(fetch.requests()[1].url).toEqual('https://facebook.com/'); 25 }); 26 27 it('calls twitter', () => { 28 fetch.mockResponse(JSON.stringify({ secret_data: '12345' })); 29 APIRequest('twitter'); 30 31 expect(fetch).toBeCalled(); // alias for expect(fetch.mock.calls.length).toEqual(1); 32 expect(fetch.requests().map((v) => v.url)).toContain('https://twitter.com/'); 33 }); 34});
By default you will want to have your fetch mock return immediately. However if you have some custom logic that needs to tests for slower servers, you can do this by passing it a function and returning a promise when your function resolves
1// api.test.js 2import { request } from './api'; 3 4describe('testing timeouts', () => { 5 it('resolves with function and timeout', async () => { 6 fetch.mockResponseOnce(() => new Promise((resolve) => setTimeout(() => resolve({ body: 'ok' }), 100))); 7 try { 8 const response = await request(); 9 expect(response).toEqual('ok'); 10 } catch (e) { 11 throw e; 12 } 13 }); 14});
In some test scenarios, you may want to temporarily disable (or enable) mocking for all requests or the next (or a certain number of) request(s). You may want to only mock fetch requests to some URLs that match a given request path while in others you may want to mock all requests except those matching a given request path. You may even want to conditionally mock based on request headers.
The conditional mock functions cause vitest-fetch-mock
to pass fetches through to the concrete fetch implementation
conditionally. Calling fetch.dontMock
, fetch.doMock
, fetch.doMockIf
or fetch.dontMockIf
overrides the default
behavior of mocking/not mocking all requests. fetch.dontMockOnce
, fetch.doMockOnce
, fetch.doMockOnceIf
and
fetch.dontMockOnceIf
only overrides the behavior for the next call to fetch
, then returns to the default behavior
(either mocking all requests or mocking the requests based on the last call to fetch.dontMock
, fetch.doMock
,
fetch.doMockIf
and fetch.dontMockIf
).
Calling fetch.resetMocks()
will return to the default behavior of mocking all fetches with a text response of empty
string.
fetch.dontMock()
- Change the default behavior to not mock any fetches until fetch.resetMocks()
or
fetch.doMock()
is calledfetch.doMock(bodyOrFunction?, responseInit?)
- Reverses fetch.dontMock()
. This is the default state after
fetch.resetMocks()
fetch.dontMockOnce()
- For the next fetch, do not mock then return to the default behavior for subsequent fetches.
Can be chained.fetch.doMockOnce(bodyOrFunction?, responseInit?)
or fetch.mockOnce
- For the next fetch, mock the response then
return to the default behavior for subsequent fetches. Can be chained.fetch.doMockIf(urlOrPredicate, bodyOrFunction?, responseInit?):fetch
or fetch.mockIf
- causes all fetches to be
not be mocked unless they match the given string/RegExp/predicate (i.e. "only mock 'fetch' if the request is for the
given URL otherwise, use the real fetch implementation")fetch.dontMockIf(urlOrPredicate, bodyOrFunction?, responseInit?):fetch
- causes all fetches to be mocked unless they
match the given string/RegExp/predicate (i.e. "don't mock 'fetch' if the request is for the given URL, otherwise mock
the request")fetch.doMockOnceIf(urlOrPredicate, bodyOrFunction?, responseInit?):fetch
or fetch.mockOnceIf
- causes the next
fetch to be mocked if it matches the given string/RegExp/predicate. Can be chained. (i.e. "only mock 'fetch' if the
next request is for the given URL otherwise, use the default behavior")fetch.dontMockOnceIf(urlOrPredicate):fetch
- causes the next fetch to be not be mocked if it matches the given
string/RegExp/predicate. Can be chained. (i.e. "don't mock 'fetch' if the next request is for the given URL, otherwise
use the default behavior")fetch.isMocking(input, init):boolean
- test utility function to see if the given url/request would be mocked. This
is not a read only operation and any "MockOnce" will evaluate (and return to the default behavior)For convenience, all the conditional mocking functions also accept optional parameters after the 1st parameter that call
mockResponse
or mockResponseOnce
respectively. This allows you to conditionally mock a response in a single call.
1vi.mock('fetch', async () => { 2 const fetchActual = globalThis.fetch; 3 const mocked = {}; 4 for (const key of Object.keys(fetchActual)) { 5 if (typeof fetchActual[key] === 'function') { 6 mocked[key] = vi.fn(fetchActual[key]); 7 } else { 8 mocked[key] = fetchActual[key]; 9 } 10 } 11 return mocked; 12}) 13 14describe('conditional mocking', () => { 15 const realResponse = 'REAL FETCH RESPONSE' 16 const mockedDefaultResponse = 'MOCKED DEFAULT RESPONSE' 17 const testUrl = defaultRequestUri 18 let fetchSpy 19 beforeEach(() => { 20 fetch.resetMocks() 21 fetch.mockResponse(mockedDefaultResponse) 22 vi.mocked(fetch) 23 .mockImplementation(async () => 24 Promise.resolve(new Response(realResponse)) 25 ) 26 }) 27 28 afterEach(() => { 29 vi.mocked(fetch).mockRestore() 30 }) 31 32 const expectMocked = async (uri, response = mockedDefaultResponse) => { 33 return expect(request(uri)).resolves.toEqual(response) 34 } 35 const expectUnmocked = async uri => { 36 return expect(request(uri)).resolves.toEqual(realResponse) 37 } 38 39 describe('once', () => { 40 it('default', async () => { 41 const otherResponse = 'other response' 42 fetch.once(otherResponse) 43 await expectMocked(defaultRequestUri, otherResponse) 44 await expectMocked() 45 }) 46 it('dont mock once then mock twice', async () => { 47 const otherResponse = 'other response' 48 fetch 49 .dontMockOnce() 50 .once(otherResponse) 51 .once(otherResponse) 52 53 await expectUnmocked() 54 await expectMocked(defaultRequestUri, otherResponse) 55 await expectMocked() 56 }) 57 }) 58 59 describe('doMockIf', () => { 60 it("doesn't mock normally", async () => { 61 fetch.doMockIf('http://foo') 62 await expectUnmocked() 63 await expectUnmocked() 64 }) 65 it('mocks when matches string', async () => { 66 fetch.doMockIf(testUrl) 67 await expectMocked() 68 await expectMocked() 69 }) 70 it('mocks when matches regex', async () => { 71 fetch.doMockIf(new RegExp(testUrl)) 72 await expectMocked() 73 await expectMocked() 74 }) 75 it('mocks when matches predicate', async () => { 76 fetch.doMockIf(input => input.url === testUrl) 77 await expectMocked() 78 await expectMocked() 79 }) 80 }) 81 82 describe('dontMockIf', () => { 83 it('mocks normally', async () => { 84 fetch.dontMockIf('http://foo') 85 await expectMocked() 86 await expectMocked() 87 }) 88 it('doesnt mock when matches string', async () => { 89 fetch.dontMockIf(testUrl) 90 await expectUnmocked() 91 await expectUnmocked() 92 }) 93 it('doesnt mock when matches regex', async () => { 94 fetch.dontMockIf(new RegExp(testUrl)) 95 await expectUnmocked() 96 await expectUnmocked() 97 }) 98 it('doesnt mock when matches predicate', async () => { 99 fetch.dontMockIf(input => input.url === testUrl) 100 await expectUnmocked() 101 await expectUnmocked() 102 }) 103 }) 104 105 describe('doMockOnceIf (default mocked)', () => { 106 it("doesn't mock normally", async () => { 107 fetch.doMockOnceIf('http://foo') 108 await expectUnmocked() 109 await expectMocked() 110 }) 111 it('mocks when matches string', async () => { 112 fetch.doMockOnceIf(testUrl) 113 await expectMocked() 114 await expectMocked() 115 }) 116 it('mocks when matches regex', async () => { 117 fetch.doMockOnceIf(new RegExp(testUrl)) 118 await expectMocked() 119 await expectMocked() 120 }) 121 it('mocks when matches predicate', async () => { 122 fetch.doMockOnceIf(input => input.url === testUrl) 123 await expectMocked() 124 await expectMocked() 125 }) 126 }) 127 128 describe('dontMockOnceIf (default mocked)', () => { 129 it('mocks normally', async () => { 130 fetch.dontMockOnceIf('http://foo') 131 await expectMocked() 132 await expectMocked() 133 }) 134 it('doesnt mock when matches string', async () => { 135 fetch.dontMockOnceIf(testUrl) 136 await expectUnmocked() 137 await expectMocked() 138 }) 139 it('doesnt mock when matches regex', async () => { 140 fetch.dontMockOnceIf(new RegExp(testUrl)) 141 await expectUnmocked() 142 await expectMocked() 143 }) 144 it('doesnt mock when matches predicate', async () => { 145 fetch.dontMockOnceIf(input => input.url === testUrl) 146 await expectUnmocked() 147 await expectMocked() 148 }) 149 }) 150 151 describe('doMockOnceIf (default unmocked)', () => { 152 beforeEach(() => { 153 fetch.dontMock() 154 }) 155 it("doesn't mock normally", async () => { 156 fetch.doMockOnceIf('http://foo') 157 await expectUnmocked() 158 await expectUnmocked() 159 }) 160 it('mocks when matches string', async () => { 161 fetch.doMockOnceIf(testUrl) 162 await expectMocked() 163 await expectUnmocked() 164 }) 165 it('mocks when matches regex', async () => { 166 fetch.doMockOnceIf(new RegExp(testUrl)) 167 await expectMocked() 168 await expectUnmocked() 169 }) 170 it('mocks when matches predicate', async () => { 171 fetch.doMockOnceIf(input => input.url === testUrl) 172 await expectMocked() 173 await expectUnmocked() 174 }) 175 }) 176 177 describe('dontMockOnceIf (default unmocked)', () => { 178 beforeEach(() => { 179 fetch.dontMock() 180 }) 181 it('mocks normally', async () => { 182 fetch.dontMockOnceIf('http://foo') 183 await expectMocked() 184 await expectUnmocked() 185 }) 186 it('doesnt mock when matches string', async () => { 187 fetch.dontMockOnceIf(testUrl) 188 await expectUnmocked() 189 await expectUnmocked() 190 }) 191 it('doesnt mock when matches regex', async () => { 192 fetch.dontMockOnceIf(new RegExp(testUrl)) 193 await expectUnmocked() 194 await expectUnmocked() 195 }) 196 it('doesnt mock when matches predicate', async () => { 197 fetch.dontMockOnceIf(input => input.url === testUrl) 198 await expectUnmocked() 199 await expectUnmocked() 200 }) 201 }) 202 203 describe('dont/do mock', () => { 204 test('dontMock', async () => { 205 fetch.dontMock() 206 await expectUnmocked() 207 await expectUnmocked() 208 }) 209 test('dontMockOnce', async () => { 210 fetch.dontMockOnce() 211 await expectUnmocked() 212 await expectMocked() 213 }) 214 test('doMock', async () => { 215 fetch.dontMock() 216 fetch.doMock() 217 await expectMocked() 218 await expectMocked() 219 }) 220 test('doMockOnce', async () => { 221 fetch.dontMock() 222 fetch.doMockOnce() 223 await expectMocked() 224 await expectUnmocked() 225 }) 226 }) 227
1vi.mock('fetch', async () => { 2 const fetchActual = globalThis.fetch; 3 const mocked = {}; 4 for (const key of Object.keys(fetchActual)) { 5 if (typeof fetchActual[key] === 'function') { 6 mocked[key] = vi.fn(fetchActual[key]); 7 } else { 8 mocked[key] = fetchActual[key]; 9 } 10 } 11 return mocked; 12}); 13 14const expectMocked = async (uri, response = mockedDefaultResponse) => { 15 return expect(request(uri)).resolves.toEqual(response); 16}; 17const expectUnmocked = async (uri) => { 18 return expect(request(uri)).resolves.toEqual(realResponse); 19}; 20 21describe('conditional mocking complex', () => { 22 const realResponse = 'REAL FETCH RESPONSE'; 23 const mockedDefaultResponse = 'MOCKED DEFAULT RESPONSE'; 24 const testUrl = defaultRequestUri; 25 let fetchSpy; 26 beforeEach(() => { 27 fetch.resetMocks(); 28 fetch.mockResponse(mockedDefaultResponse); 29 vi.mocked(fetch).mockImplementation(async () => Promise.resolve(new Response(realResponse))); 30 }); 31 32 afterEach(() => { 33 vi.mocked(fetch).mockRestore(); 34 }); 35 36 describe('complex example', () => { 37 const alternativeUrl = 'http://bar'; 38 const alternativeBody = 'ALTERNATIVE RESPONSE'; 39 beforeEach(() => { 40 fetch 41 // .mockResponse(mockedDefaultResponse) // set above - here for clarity 42 .mockResponseOnce('1') // 1 43 .mockResponseOnce('2') // 2 44 .mockResponseOnce(async (uri) => (uri === alternativeUrl ? alternativeBody : '3')) // 3 45 .mockResponseOnce('4') // 4 46 .mockResponseOnce('5') // 5 47 .mockResponseOnce(async (uri) => (uri === alternativeUrl ? alternativeBody : mockedDefaultResponse)); // 6 48 }); 49 50 describe('default (`doMock`)', () => { 51 beforeEach(() => { 52 fetch 53 // .doMock() // the default - here for clarify 54 .dontMockOnceIf(alternativeUrl) 55 .doMockOnceIf(alternativeUrl) 56 .doMockOnce() 57 .dontMockOnce(); 58 }); 59 60 test('defaultRequestUri', async () => { 61 await expectMocked(defaultRequestUri, '1'); // 1 62 await expectUnmocked(defaultRequestUri); // 2 63 await expectMocked(defaultRequestUri, '3'); // 3 64 await expectUnmocked(defaultRequestUri); // 4 65 // after .once('..') 66 await expectMocked(defaultRequestUri, '5'); // 5 67 await expectMocked(defaultRequestUri, mockedDefaultResponse); // 6 68 // default 'isMocked' (not 'Once') 69 await expectMocked(defaultRequestUri, mockedDefaultResponse); // 7 70 }); 71 72 test('alternativeUrl', async () => { 73 await expectUnmocked(alternativeUrl); // 1 74 await expectMocked(alternativeUrl, '2'); // 2 75 await expectMocked(alternativeUrl, alternativeBody); // 3 76 await expectUnmocked(alternativeUrl); // 4 77 // after .once('..') 78 await expectMocked(alternativeUrl, '5'); // 5 79 await expectMocked(alternativeUrl, alternativeBody); // 6 80 // default 'isMocked' (not 'Once') 81 await expectMocked(alternativeUrl, mockedDefaultResponse); // 7 82 }); 83 }); 84 85 describe('dontMock', () => { 86 beforeEach(() => { 87 fetch.dontMock().dontMockOnceIf(alternativeUrl).doMockOnceIf(alternativeUrl).doMockOnce().dontMockOnce(); 88 }); 89 90 test('defaultRequestUri', async () => { 91 await expectMocked(defaultRequestUri, '1'); // 1 92 await expectUnmocked(defaultRequestUri); // 2 93 await expectMocked(defaultRequestUri, '3'); // 3 94 await expectUnmocked(defaultRequestUri); // 4 95 // after .once('..') 96 await expectUnmocked(defaultRequestUri); // 5 97 await expectUnmocked(defaultRequestUri); // 6 98 // default 'isMocked' (not 'Once') 99 await expectUnmocked(defaultRequestUri); // 7 100 }); 101 102 test('alternativeUrl', async () => { 103 await expectUnmocked(alternativeUrl); // 1 104 await expectMocked(alternativeUrl, '2'); // 2 105 await expectMocked(alternativeUrl, alternativeBody); // 3 106 await expectUnmocked(alternativeUrl); // 4 107 // after .once('..') 108 await expectUnmocked(alternativeUrl); // 5 109 await expectUnmocked(alternativeUrl); // 6 110 // default 'isMocked' (not 'Once') 111 await expectUnmocked(alternativeUrl); // 7 112 }); 113 }); 114 115 describe('doMockIf(alternativeUrl)', () => { 116 beforeEach(() => { 117 fetch 118 .doMockIf(alternativeUrl) 119 .dontMockOnceIf(alternativeUrl) 120 .doMockOnceIf(alternativeUrl) 121 .doMockOnce() 122 .dontMockOnce(); 123 }); 124 125 test('defaultRequestUri', async () => { 126 await expectMocked(defaultRequestUri, '1'); // 1 127 await expectUnmocked(defaultRequestUri); // 2 128 await expectMocked(defaultRequestUri, '3'); // 3 129 await expectUnmocked(defaultRequestUri); // 4 130 // after .once('..') 131 await expectUnmocked(defaultRequestUri); // 5 132 await expectUnmocked(defaultRequestUri); // 6 133 // default 'isMocked' (not 'Once') 134 await expectUnmocked(defaultRequestUri); // 7 135 }); 136 137 test('alternativeUrl', async () => { 138 await expectUnmocked(alternativeUrl); // 1 139 await expectMocked(alternativeUrl, '2'); // 2 140 await expectMocked(alternativeUrl, alternativeBody); // 3 141 await expectUnmocked(alternativeUrl); // 4 142 // after .once('..') 143 await expectMocked(alternativeUrl, '5'); // 5 144 await expectMocked(alternativeUrl, alternativeBody); // 6 145 // default 'isMocked' (not 'Once') 146 await expectMocked(alternativeUrl, mockedDefaultResponse); // 7 147 }); 148 }); 149 150 describe('dontMockIf(alternativeUrl)', () => { 151 beforeEach(() => { 152 fetch 153 .dontMockIf(alternativeUrl) 154 .dontMockOnceIf(alternativeUrl) 155 .doMockOnceIf(alternativeUrl) 156 .doMockOnce() 157 .dontMockOnce(); 158 }); 159 160 test('defaultRequestUri', async () => { 161 await expectMocked(defaultRequestUri, '1'); // 1 162 await expectUnmocked(defaultRequestUri); // 2 163 await expectMocked(defaultRequestUri, '3'); // 3 164 await expectUnmocked(defaultRequestUri); // 4 165 // after .once('..') 166 await expectMocked(defaultRequestUri, '5'); // 5 167 await expectMocked(defaultRequestUri, mockedDefaultResponse); // 6 168 // default 'isMocked' (not 'Once') 169 await expectMocked(defaultRequestUri, mockedDefaultResponse); // 7 170 }); 171 172 test('alternativeUrl', async () => { 173 await expectUnmocked(alternativeUrl); // 1 174 await expectMocked(alternativeUrl, '2'); // 2 175 await expectMocked(alternativeUrl, alternativeBody); // 3 176 await expectUnmocked(alternativeUrl); // 4 177 // after .once('..') 178 await expectUnmocked(alternativeUrl); // 5 179 await expectUnmocked(alternativeUrl); // 6 180 // default 'isMocked' (not 'Once') 181 await expectUnmocked(alternativeUrl); // 7 182 }); 183 }); 184 }); 185});
No vulnerabilities found.
No security vulnerabilities found.