vitest-fetch-mock
Version:
fetch mock for vitest
323 lines • 12 kB
JavaScript
import { vi as vitest } from 'vitest';
class FetchMockObject {
mockedFetch;
originalFetch;
chainingResultProvider;
isMocking = vitest.fn(always(true));
constructor(mockedFetch, originalFetch, chainingResultProvider) {
this.mockedFetch = mockedFetch;
this.originalFetch = originalFetch;
this.chainingResultProvider = chainingResultProvider;
}
// enable/disable
enableMocks() {
globalThis.fetch = this.mockedFetch;
globalThis.fetchMock = this.chainingResultProvider();
return this.chainingResultProvider();
}
disableMocks() {
globalThis.fetch = this.originalFetch;
return this.chainingResultProvider();
}
// reset
resetMocks() {
this.mockedFetch.mockRestore();
return this.chainingResultProvider();
}
// mocking functions
mockResponse(responseProvider, params) {
this.mockedFetch.mockImplementation((input, requestInit) => {
if (!this.isMocking(input, requestInit)) {
return this.originalFetch(input, requestInit);
}
const request = normalizeRequest(input, requestInit);
return buildResponse(request, responseProvider, params);
});
return this.chainingResultProvider();
}
mockResponseOnce(responseProvider, params) {
this.mockedFetch.mockImplementationOnce((input, requestInit) => {
if (!this.isMocking(input, requestInit)) {
return this.originalFetch(input, requestInit);
}
const request = normalizeRequest(input, requestInit);
return buildResponse(request, responseProvider, params);
});
return this.chainingResultProvider();
}
mockResponseIf(urlOrPredicate, responseProvider, params) {
this.mockedFetch.mockImplementation((input, requestInit) => {
if (!this.isMocking(input, requestInit)) {
return this.originalFetch(input, requestInit);
}
const request = normalizeRequest(input, requestInit);
return requestMatches(request, urlOrPredicate)
? buildResponse(request, responseProvider, params)
: this.originalFetch(input, requestInit);
});
return this.chainingResultProvider();
}
mockResponseOnceIf(urlOrPredicate, responseProvider, params) {
this.isMocking.mockImplementationOnce((input, requestInit) => requestMatches(normalizeRequest(input, requestInit), urlOrPredicate));
this.mockedFetch.mockImplementationOnce((input, requestInit) => {
if (!this.isMocking(input, requestInit)) {
return this.originalFetch(input, requestInit);
}
const request = normalizeRequest(input, requestInit);
return requestMatches(request, urlOrPredicate)
? buildResponse(request, responseProvider, params)
: this.originalFetch(input, requestInit);
});
return this.chainingResultProvider();
}
mockResponses(...responses) {
responses.forEach((response) => {
if (Array.isArray(response)) {
const [body, init] = response;
this.mockedFetch.mockImplementationOnce((input) => {
if (!this.isMocking(input)) {
return this.originalFetch(input);
}
const request = normalizeRequest(input);
return buildResponse(request, body, init);
});
}
else {
this.mockedFetch.mockImplementationOnce((input) => {
if (!this.isMocking(input)) {
return this.originalFetch(input);
}
const request = normalizeRequest(input);
return buildResponse(request, response);
});
}
});
return this.chainingResultProvider();
}
// abort
mockAbort() {
this.mockedFetch.mockImplementation(() => abortAsync());
return this.chainingResultProvider();
}
mockAbortOnce() {
this.mockedFetch.mockImplementationOnce(() => abortAsync());
return this.chainingResultProvider();
}
// reject (error)
mockReject(error) {
this.mockedFetch.mockImplementation((input, requestInit) => normalizeError(normalizeRequest(input, requestInit), error));
return this.chainingResultProvider();
}
mockRejectOnce(error) {
this.mockedFetch.mockImplementationOnce((input, requestInit) => normalizeError(normalizeRequest(input, requestInit), error));
return this.chainingResultProvider();
}
// enable/disable
doMock(responseProvider, params) {
this.isMocking.mockImplementation(always(true));
if (responseProvider) {
this.mockResponse(responseProvider, params);
}
return this.chainingResultProvider();
}
doMockOnce(responseProvider, params) {
this.isMocking.mockImplementationOnce(always(true));
if (responseProvider) {
this.mockResponseOnce(responseProvider, params);
}
return this.chainingResultProvider();
}
doMockIf(urlOrPredicate, responseProvider, params) {
this.isMocking.mockImplementation((input, requestInit) => requestMatches(normalizeRequest(input, requestInit), urlOrPredicate));
if (responseProvider) {
this.mockResponse(responseProvider, params);
}
return this.chainingResultProvider();
}
doMockOnceIf(urlOrPredicate, responseProvider, params) {
this.isMocking.mockImplementationOnce((input, requestInit) => requestMatches(normalizeRequest(input, requestInit), urlOrPredicate));
if (responseProvider) {
this.mockResponseOnce(responseProvider, params);
}
return this.chainingResultProvider();
}
dontMock() {
this.isMocking.mockImplementation(always(false));
return this.chainingResultProvider();
}
dontMockOnce() {
this.isMocking.mockImplementationOnce(always(false));
return this.chainingResultProvider();
}
dontMockIf(urlOrPredicate) {
this.isMocking.mockImplementation((input, requestInit) => requestNotMatches(normalizeRequest(input, requestInit), urlOrPredicate));
return this.chainingResultProvider();
}
dontMockOnceIf(urlOrPredicate) {
this.isMocking.mockImplementationOnce((input, requestInit) => requestNotMatches(normalizeRequest(input, requestInit), urlOrPredicate));
return this.chainingResultProvider();
}
// recording
requests() {
return this.mockedFetch.mock.calls
.map(([input, requestInit]) => {
try {
return normalizeRequest(input, requestInit);
}
catch (e) {
return undefined;
}
})
.filter((it) => it !== undefined);
}
// aliases
/**
* alias for mockResponseOnce
*/
once(responseProvider, params) {
return this.mockResponseOnce(responseProvider, params);
}
/**
* alias for doMockOnce
*/
mockOnce(responseProvider, params) {
return this.doMockOnce(responseProvider, params);
}
/**
* alias for doMockIf
*/
mockIf(urlOrPredicate, responseProvider, params) {
return this.doMockIf(urlOrPredicate, responseProvider, params);
}
/**
* alias for doMockOnceIf
*/
mockOnceIf(urlOrPredicate, responseProvider, params) {
return this.doMockOnceIf(urlOrPredicate, responseProvider, params);
}
}
// factory
export default function createFetchMock(vi) {
const isMocking = vi.fn(always(true));
const originalFetch = globalThis.fetch;
const mockedFetch = vi.fn((input, requestInit) => {
if (!isMocking(input, requestInit)) {
return originalFetch(input, requestInit);
}
return buildResponse(normalizeRequest(input, requestInit), '');
});
const fetchMock = mockedFetch;
const fetchMockObject = new FetchMockObject(mockedFetch, originalFetch, () => fetchMock);
copyMethods(fetchMockObject, fetchMock);
return mockedFetch;
}
function requestMatches(request, urlOrPredicate) {
if (urlOrPredicate instanceof RegExp) {
return urlOrPredicate.test(request.url);
}
else if (typeof urlOrPredicate === 'string') {
return request.url === urlOrPredicate;
}
else {
return urlOrPredicate(request);
}
}
function requestNotMatches(request, urlOrPredicate) {
return !requestMatches(request, urlOrPredicate);
}
// Node 18 does not support URL.canParse()
export function canParseURL(url) {
try {
new URL(url);
return true;
}
catch (err) {
return false;
}
}
// Node Requests cannot be relative
function resolveInput(input) {
if (canParseURL(input))
return input;
// Window context
if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
return new URL(input, window.document.baseURI).toString();
}
// Worker context
if (typeof location !== 'undefined') {
return new URL(input, location.origin).toString();
}
return input;
}
function normalizeRequest(input, requestInit) {
if (input instanceof Request) {
if (input.signal && input.signal.aborted) {
abort();
}
return input;
}
else if (typeof input === 'string') {
if (requestInit && requestInit.signal && requestInit.signal.aborted) {
abort();
}
return new Request(resolveInput(input), requestInit);
}
else {
if (requestInit && requestInit.signal && requestInit.signal.aborted) {
abort();
}
return new Request(resolveInput(input.toString()), requestInit);
}
}
async function buildResponse(request, responseProvider, params) {
const response = await normalizeResponse(request, responseProvider, params);
if (request.signal && request.signal.aborted) {
abort();
}
return response;
}
async function normalizeResponse(request, responseProvider, params) {
const responseLike = typeof responseProvider === 'function' ? await responseProvider(request) : responseProvider;
if (responseLike instanceof Response) {
return responseLike;
}
else if (typeof responseLike === 'string' || responseLike === null || responseLike === undefined) {
return new Response(responseLike, params);
}
else {
return patchUrl(new Response(responseLike.body, { ...params, ...responseLike }), responseLike.url ?? params?.url);
}
}
// see: https://stackoverflow.com/questions/70002424/url-is-empty-when-defining-a-response-object
function patchUrl(response, url) {
if (url) {
Object.defineProperty(response, 'url', { value: url });
}
return response;
}
function always(toggle) {
return () => toggle;
}
const normalizeError = async (request, errorOrFunction) => errorOrFunction instanceof Error
? Promise.reject(errorOrFunction)
: typeof errorOrFunction === 'function'
? buildResponse(request, errorOrFunction)
: Promise.reject(errorOrFunction);
function abortError() {
return new DOMException('The operation was aborted.', 'AbortError');
}
function abort() {
throw abortError();
}
function abortAsync() {
return Promise.reject(abortError());
}
function copyMethods(source, target) {
Object.getOwnPropertyNames(FetchMockObject.prototype).forEach((propertyName) => {
const propertyFromSource = source[propertyName];
if (propertyName !== 'constructor' && typeof propertyFromSource === 'function') {
target[propertyName] = propertyFromSource.bind(source);
}
});
}
//# sourceMappingURL=index.js.map