UNPKG

hungry-fetch

Version:
170 lines (142 loc) 3.85 kB
/** * This monkeypatches fetch and stores all requests send with fetch. * fetch calls are simply resolved with no data. * * With this you can test what your code is sending via fetch. */ import FetchCall from './fetch-call'; import { testWildcardPattern } from './url-pattern'; let fetchRequests: FetchCall[] = []; let mockResponses: ResponseData[] = []; export type Body = Record<string, any> | string; export type ResponseData = { pattern: string; body: Record<string, any> | string; config: { contentType?: string; headers?: { [key: string]: string }; status?: number; }; resolve?: boolean; }; function getResponse(responseData: ResponseData) { let body; let contentType; if (typeof responseData.body === 'object') { // json response body = JSON.stringify(responseData.body); contentType = 'application/json'; } else { // plain text body = responseData.body; contentType = 'text/plain'; } const { config } = responseData; return new Response(body, { headers: { 'Content-Type': config.contentType || contentType, ...(config.headers || {}), }, status: config.status || 200, }); } function testUrl(url: string, pattern: string) { // exact match if (url === pattern) { return 1.0; } // any pattern if (pattern === '*') { return 0.1; } // match using wildcards if (testWildcardPattern(url, pattern)) { return 0.5; } // no match return 0; } function getMockResponse(url: string): ResponseData | null { if (mockResponses.length === 0) return null; return mockResponses.reduce<ResponseData | null>((acc, cur) => { const curWeight = testUrl(url, cur.pattern); if (curWeight === 0) { return acc; } if (acc === null || curWeight > testUrl(url, acc.pattern)) { return cur; } return acc; }, null); } export function clear() { fetchRequests = []; mockResponses = []; } export function calls() { return fetchRequests; } export function lastCall() { if (fetchRequests.length === 0) { throw new Error('fetch(…) has not been called!'); } return fetchRequests[fetchRequests.length - 1]; } export function singleCall() { if (fetchRequests.length !== 1) { throw new Error( `fetch(…) not called exactly one time! It was called ${fetchRequests.length} times.` ); } return lastCall(); } /** * Mock a response with a url matching the specified pattern. * * The pattern supports wildcards for parts of the url by using an asterisk (\*). * Example: /category/*&#47;details * * You may also use a single asterisk "*" to match all routes. * * The matchers are weighted so that a perfect match has the highest priority. * A match using wildcard components has the second highest prority. The * single asterisk matcher has the lowest priority. This helps to configure * default responses. * * @param urlMatcher The matcher incoming urls will be checked against. * @param body The body of the response. * @param config An optional config. * @param resolve Set to false to reject matching fetch calls. */ export function mockResponse( pattern: string, body: Body, config = {}, resolve = true ) { mockResponses.push({ pattern, body, resolve, config, }); } (typeof window !== 'undefined' ? window : (global as any)).fetch = ( url: string, request: RequestInit, ...args: any[] ) => new Promise<void | Response>((resolve, reject) => { fetchRequests.push(new FetchCall(url, request, args)); const mockedResponse = getMockResponse(url); if (mockedResponse) { const response = getResponse(mockedResponse); if (mockedResponse.resolve) { resolve(response); } else { reject(new Error('Failed to fetch')); } } else { resolve(); } });