@aryzing/bun-mock-fetch
Advanced tools
Comparing version 0.1.0 to 0.2.0
@@ -1,5 +0,2 @@ | ||
import { type MockOptions } from "./types.js"; | ||
/** | ||
* The default value for mock options. | ||
*/ | ||
export declare const defaultMockOptions: MockOptions; | ||
import { type MockResponseOptions } from "./types.js"; | ||
export declare const defaultMockOptions: MockResponseOptions; |
import {} from "./types.js"; | ||
/** | ||
* The default value for mock options. | ||
*/ | ||
export const defaultMockOptions = { | ||
response: { | ||
data: null, | ||
// @ts-ignore // Not sure why, the Headers type is not recognized. | ||
headers: new Headers(), | ||
status: 200, | ||
}, | ||
data: null, | ||
status: 200, | ||
}; | ||
//# sourceMappingURL=constants.js.map |
@@ -1,9 +0,7 @@ | ||
import { type MockOptions } from "./types.js"; | ||
import type { MockResponseOptions, RequestMatcher } from "./types.js"; | ||
export declare function setIsVerbose(value: boolean): void; | ||
type Minimatch = string; | ||
export type URLMatch = Minimatch | RegExp; | ||
/** | ||
* Mock the fetch method. The most recently defined matching mock will be used. | ||
*/ | ||
export declare const mockFetch: (urlMatch: URLMatch, options?: MockOptions) => void; | ||
export declare const mockFetch: (requestMatcher: RequestMatcher, mockResponseOptions?: MockResponseOptions) => void; | ||
/** | ||
@@ -18,2 +16,1 @@ * Clear the fetch mock. | ||
export declare function setIsUsingBuiltInFetchFallback(value: boolean): void; | ||
export {}; |
128
dist/mock.js
import { minimatch } from "minimatch"; | ||
import { defaultMockOptions } from "./constants.js"; | ||
import {} from "./types.js"; | ||
import { makeSimplifiedResponse } from "./utils.js"; | ||
@@ -10,10 +9,2 @@ let originalFetch; | ||
} | ||
// | { | ||
// type: "function"; | ||
// fn: ( | ||
// input: Parameters<typeof fetch>[0], | ||
// init: Parameters<typeof fetch>[1], | ||
// ) => boolean; | ||
// options: MockOptions; | ||
// }; | ||
/** | ||
@@ -23,21 +14,36 @@ * The cache for registered mocked requests. | ||
const mockedRequests = []; | ||
// | (( | ||
// input: Parameters<typeof fetch>[0], | ||
// init: Parameters<typeof fetch>[1], | ||
// ) => boolean); | ||
/** | ||
* Mock the fetch method. The most recently defined matching mock will be used. | ||
*/ | ||
export const mockFetch = (urlMatch, options = defaultMockOptions) => { | ||
if (urlMatch instanceof RegExp) { | ||
mockedRequests.unshift({ type: "regexp", regexp: urlMatch, options }); | ||
export const mockFetch = (requestMatcher, mockResponseOptions = defaultMockOptions) => { | ||
if (requestMatcher instanceof RegExp) { | ||
mockedRequests.unshift({ | ||
type: "regexp", | ||
regexp: requestMatcher, | ||
mockResponseOptions, | ||
}); | ||
} | ||
else if (typeof urlMatch === "string") { | ||
mockedRequests.unshift({ type: "minimatch", minimatch: urlMatch, options }); | ||
else if (typeof requestMatcher === "string") { | ||
mockedRequests.unshift({ | ||
type: "stringLiteralOrMinimatch", | ||
value: requestMatcher, | ||
mockResponseOptions, | ||
}); | ||
} | ||
// else if (typeof urlMatch === "function") { | ||
// mockedRequests.unshift({ type: "function", fn: urlMatch, options }); | ||
// } | ||
else if (typeof requestMatcher === "function") { | ||
mockedRequests.unshift({ | ||
type: "function", | ||
fn: requestMatcher, | ||
mockResponseOptions, | ||
}); | ||
} | ||
else if (typeof requestMatcher === "object") { | ||
mockedRequests.unshift({ | ||
type: "detailed", | ||
matcher: requestMatcher, | ||
mockResponseOptions, | ||
}); | ||
} | ||
else { | ||
throw new Error("Invalid URL match type"); | ||
throw new Error("Invalid matcher."); | ||
} | ||
@@ -72,52 +78,45 @@ if (!originalFetch) { | ||
/** | ||
* A mocked fetch method. | ||
* The mocked fetch method. | ||
*/ | ||
const mockedFetch = async (input, init) => { | ||
const path = input instanceof Request ? input.url : input.toString(); | ||
const requestUrl = input instanceof Request ? input.url : input.toString(); | ||
if (isVerbose) | ||
console.debug("[BMF]: Mocked fetch called with path:", path); | ||
console.debug("[BMF]: Mocked fetch called with path:", requestUrl); | ||
for (const mockedRequest of mockedRequests) { | ||
switch (mockedRequest.type) { | ||
case "regexp": { | ||
// Match path | ||
if (!mockedRequest.regexp.test(path)) | ||
if (!mockedRequest.regexp.test(requestUrl)) | ||
continue; | ||
// Match method | ||
const optionsMethod = mockedRequest.options.method; | ||
const requestMethod = (init && init.method) || "GET"; | ||
if (optionsMethod && | ||
optionsMethod.toLowerCase() !== requestMethod.toLowerCase()) | ||
return makeSimplifiedResponse(requestUrl, mockedRequest.mockResponseOptions); | ||
} | ||
case "stringLiteralOrMinimatch": { | ||
if (requestUrl !== mockedRequest.value && | ||
!minimatch(requestUrl, mockedRequest.value)) | ||
continue; | ||
// Match headers | ||
const optionsHeaders = mockedRequest.options.headers; | ||
if (optionsHeaders) { | ||
const inputHeaders = input instanceof Request ? input.headers : new Headers(); | ||
const initHeaders = new Headers(init?.headers); | ||
const requestHeaders = new Headers([...inputHeaders, ...initHeaders]); | ||
const headersMatch = [...Object.entries(optionsHeaders)].every(([optionHeaderName, optionHeaderValue]) => { | ||
const requestHeaderValue = requestHeaders.get(optionHeaderName); | ||
return requestHeaderValue === optionHeaderValue; | ||
}); | ||
if (!headersMatch) | ||
return makeSimplifiedResponse(requestUrl, mockedRequest.mockResponseOptions); | ||
} | ||
case "function": { | ||
if (!mockedRequest.fn(input, init)) | ||
continue; | ||
return makeSimplifiedResponse(requestUrl, mockedRequest.mockResponseOptions); | ||
} | ||
case "detailed": { | ||
const { matcher, mockResponseOptions } = mockedRequest; | ||
const { url, method, headers } = matcher; | ||
if (typeof url === "string") { | ||
if (url !== requestUrl && !minimatch(requestUrl, url)) | ||
continue; | ||
} | ||
return makeSimplifiedResponse(path, mockedRequest.options); | ||
} | ||
case "minimatch": { | ||
// Match path | ||
if (!minimatch(path, mockedRequest.minimatch)) | ||
else if (url instanceof RegExp) { | ||
if (!url.test(requestUrl)) | ||
continue; | ||
} | ||
if (method && | ||
method.toLowerCase() !== (init?.method || "GET").toLowerCase()) | ||
continue; | ||
// Match method | ||
const optionsMethod = mockedRequest.options.method; | ||
const requestMethod = (init && init.method) || "GET"; | ||
if (optionsMethod && | ||
optionsMethod.toLowerCase() !== requestMethod.toLowerCase()) | ||
continue; | ||
// Match headers | ||
const optionsHeaders = mockedRequest.options.headers; | ||
if (optionsHeaders) { | ||
if (headers) { | ||
const inputHeaders = input instanceof Request ? input.headers : new Headers(); | ||
const initHeaders = new Headers(init?.headers); | ||
const requestHeaders = new Headers([...inputHeaders, ...initHeaders]); | ||
const headersMatch = [...Object.entries(optionsHeaders)].every(([optionHeaderName, optionHeaderValue]) => { | ||
const headersMatch = [...Object.entries(headers)].every(([optionHeaderName, optionHeaderValue]) => { | ||
const requestHeaderValue = requestHeaders.get(optionHeaderName); | ||
@@ -129,3 +128,3 @@ return requestHeaderValue === optionHeaderValue; | ||
} | ||
return makeSimplifiedResponse(path, mockedRequest.options); | ||
return makeSimplifiedResponse(requestUrl, mockResponseOptions); | ||
} | ||
@@ -135,12 +134,15 @@ } | ||
if (isVerbose) | ||
console.debug("[BMF]: No matching mock found for path:", path); | ||
console.debug("[BMF]: No matching mock found for request:", requestUrl); | ||
if (isUsingBuiltInFetchFallback) { | ||
if (isVerbose) | ||
console.debug("[BMF]: Using built-in fetch for path:", path); | ||
console.debug("[BMF]: Using built-in fetch for request:", requestUrl); | ||
return originalFetch(input, init); | ||
} | ||
if (isVerbose) | ||
console.debug("[BMF]: Rejecting with 404 for path:", path); | ||
return Promise.reject(makeSimplifiedResponse(path, { response: { status: 404 } })); | ||
console.debug("[BMF]: Responding with 404:", requestUrl); | ||
return Promise.resolve(makeSimplifiedResponse(requestUrl, { | ||
status: 404, | ||
data: `{"bun-mock-fetch":"No matching mocks."}`, | ||
})); | ||
}; | ||
//# sourceMappingURL=mock.js.map |
@@ -1,18 +0,12 @@ | ||
/** | ||
* The options for a mocked request. Partial implementation of RequestInit with | ||
* the addition of "data" property which value will be returned from the mock. | ||
*/ | ||
export type MockOptions = { | ||
export type Glob = string; | ||
export type DetailedMatcher = { | ||
url?: string | Glob | RegExp; | ||
method?: string; | ||
headers?: Record<string, string>; | ||
method?: Request["method"]; | ||
response?: MockResponse; | ||
}; | ||
/** | ||
* The response for a mocked request. Partial implementation of Response with | ||
* the addition of "data" property which value will be returned from the mock. | ||
*/ | ||
export interface MockResponse { | ||
data?: any; | ||
export type RequestMatcher = string | Glob | RegExp | ((input: Parameters<typeof fetch>[0], init: Parameters<typeof fetch>[1]) => boolean) | DetailedMatcher; | ||
export type MockResponseOptions = { | ||
data?: unknown; | ||
status?: number; | ||
headers?: Record<string, string>; | ||
} | ||
}; |
@@ -1,10 +0,3 @@ | ||
import { type MockOptions } from "./types.js"; | ||
import { type MockResponseOptions } from "./types.js"; | ||
export type SimplifiedResponse = Omit<Response, "type" | "body" | "arrayBuffer" | "blob" | "formData" | "clone">; | ||
/** | ||
* Returns an object similar to Response class. | ||
* @param status - The HTTP status code of the response. | ||
* @param url - The URL of the request. | ||
* @param options - The options for the mocked request. | ||
* @returns An object similar to Response class. | ||
*/ | ||
export declare function makeSimplifiedResponse(url: string, options?: MockOptions): SimplifiedResponse; | ||
export declare function makeSimplifiedResponse(url: string, options?: MockResponseOptions): SimplifiedResponse; |
import { defaultMockOptions as defaultMockOptions } from "./constants.js"; | ||
import {} from "./types.js"; | ||
/** | ||
* Returns an object similar to Response class. | ||
* @param status - The HTTP status code of the response. | ||
* @param url - The URL of the request. | ||
* @param options - The options for the mocked request. | ||
* @returns An object similar to Response class. | ||
*/ | ||
export function makeSimplifiedResponse(url, options = defaultMockOptions) { | ||
const responseHeaders = new Headers(options.response?.headers); | ||
const status = options.response?.status ?? 200; | ||
const responseHeaders = new Headers(options.headers); | ||
const status = options.status ?? 200; | ||
const ok = status >= 200 && status < 300; | ||
const body = options.response?.data; | ||
const body = options.data; | ||
return { | ||
@@ -21,3 +14,3 @@ ok, | ||
headers: responseHeaders, | ||
text: () => Promise.resolve(body), | ||
text: () => Promise.resolve(`${body}`), | ||
json: () => Promise.resolve(body), | ||
@@ -24,0 +17,0 @@ redirected: false, |
{ | ||
"name": "@aryzing/bun-mock-fetch", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"author": "Eduard Bardají Puig <@aryzing>", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -9,6 +9,12 @@ # `@aryzing/bun-mock-fetch` | ||
Example usage: | ||
Basic usage: | ||
```typescript | ||
// Returns 200 OK by default | ||
mockFetch(requestMatcher, optionalMockResponseOptions); | ||
``` | ||
Request matcher examples: | ||
```typescript | ||
// Simple string matching | ||
mockFetch("https://example.com"); | ||
@@ -22,11 +28,14 @@ | ||
mockFetch("https://example.com/foo/**", { | ||
// Must have these headers. | ||
headers: { "x-example-header": "example-value" }, | ||
// Must use this method. | ||
method: "GET", | ||
response: { | ||
data: JSON.stringify({ foo: "bar" }), | ||
headers: { "Content-Type": "application/json" }, | ||
status: 200, | ||
// Using a function | ||
mockFetch((input, init) => input.url === "https://example.com"); | ||
// Using a detailed matcher object. All properties are optional. | ||
mockFetch({ | ||
// Must match this string, glob, or regex | ||
url: "https://example.com", | ||
// Must match this method (case-insensitive). | ||
method: "POST", | ||
// Must include these headers (case-insensitive) and match their values. | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
@@ -36,2 +45,15 @@ }); | ||
Response options example: | ||
```typescript | ||
mockFetch(/.*example.*/, { | ||
// The expected resolved value of Response.json() or Response.text(). | ||
data: "Hello, world!", | ||
status: 200, | ||
headers: { | ||
"Content-Type": "text/plain", | ||
}, | ||
}); | ||
``` | ||
Example in tests, | ||
@@ -63,3 +85,3 @@ | ||
The `mockFetch` method may be called several times to define multiple mocks. Requests will at be matched at most against one mock, with later mocks take precendece over earlier mocks. | ||
Each call to `mockFetch` defines a new mock. At most one mock is used,, with each mock taking precendece over previously defined mocks. | ||
@@ -66,0 +88,0 @@ By default, requests that aren't matched against any mock definitions are forwarded to the native built-in fetch. This behavior can be modified using `setIsUsingBuiltInFetchFallback()`. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
96
15992
204