fix(tests): address edge cases in mock infrastructure

- Prevent spy leak on double mockFetch() call
- Differentiate body types (URLSearchParams, ArrayBuffer, Blob, ReadableStream)
- Fix route path parsing to preserve spaces
- Escape regex special chars before wildcard replacement
- Remove unused MockResponse.error and MockRoute interface
This commit is contained in:
kaitranntt
2026-01-23 23:41:49 -05:00
parent 5c83429a79
commit 2b91c40e37
3 changed files with 24 additions and 16 deletions
+16 -2
View File
@@ -27,7 +27,13 @@ let capturedRequests: CapturedRequest[] = [];
* afterEach(() => restoreFetch());
*/
export function mockFetch(handlers: MockFetchHandler[]): void {
// Store original if not already mocked
// Restore previous mock if exists (prevents spy leak on double-call)
if (mockInstance) {
mockInstance.mockRestore();
mockInstance = null;
}
// Store original if not already stored
if (!originalFetch) {
originalFetch = globalThis.fetch;
}
@@ -64,12 +70,20 @@ export function mockFetch(handlers: MockFetchHandler[]): void {
}
}
// Extract body
// Extract body with type differentiation
if (init?.body) {
if (typeof init.body === 'string') {
captured.body = init.body;
} else if (init.body instanceof FormData) {
captured.body = '[FormData]';
} else if (init.body instanceof URLSearchParams) {
captured.body = '[URLSearchParams]';
} else if (init.body instanceof ArrayBuffer || ArrayBuffer.isView(init.body)) {
captured.body = '[ArrayBuffer]';
} else if (init.body instanceof Blob) {
captured.body = '[Blob]';
} else if (typeof init.body === 'object' && 'getReader' in init.body) {
captured.body = '[ReadableStream]';
} else {
captured.body = '[Binary]';
}
+8 -2
View File
@@ -55,12 +55,18 @@ export function createMockHttpServer(config: MockHttpServerConfig): MockHttpServ
// Try pattern matching for routes with wildcards
for (const [key, response] of Object.entries(routes)) {
const [routeMethod, routePath] = key.split(' ', 2);
// Split on first space only to preserve spaces in path
const spaceIndex = key.indexOf(' ');
if (spaceIndex === -1) continue;
const routeMethod = key.slice(0, spaceIndex);
const routePath = key.slice(spaceIndex + 1);
if (routeMethod !== method) continue;
// Check if route path is a pattern (contains *)
if (routePath.includes('*')) {
const pattern = routePath.replace(/\*/g, '.*');
// Escape regex special chars, then replace * with .*
const escaped = routePath.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
const pattern = escaped.replace(/\*/g, '.*');
const regex = new RegExp(`^${pattern}$`);
if (regex.test(path)) {
return response;
-12
View File
@@ -21,23 +21,11 @@ export interface MockResponse {
headers?: Record<string, string>;
/** Delay in ms before responding (for timeout testing) */
delay?: number;
/** Throw error instead of responding */
error?: Error;
}
/** Route key format: "METHOD /path" */
export type RouteKey = `${HttpMethod} ${string}`;
/** Mock route configuration */
export interface MockRoute {
/** HTTP method */
method: HttpMethod;
/** URL path pattern (string or regex) */
path: string | RegExp;
/** Response to return */
response: MockResponse;
}
/** Configuration for createMockHttpServer */
export interface MockHttpServerConfig {
/** Route definitions - key format: "METHOD /path" */