Created
May 26, 2025 09:47
-
-
Save pachun/b6c66fe30db4f38f4bbbf4ea01de5d82 to your computer and use it in GitHub Desktop.
Replace nock() with mswNock() (also, uninstall nock and install msw in your development dependencies)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { http, HttpResponse, HttpHandler, StrictRequest } from "msw" | |
import { setupServer } from "msw/native" | |
const server = setupServer() | |
beforeAll(() => server.listen()) | |
afterEach(() => server.resetHandlers()) | |
afterAll(() => server.close()) | |
type Headers = Record<string, string> | |
type QueryParams = Record<string, string | null> | |
type HttpMethod = "get" | "post" | "put" | "delete" | |
export class MSWNock { | |
private method: HttpMethod = "get" | |
private fullUrl = "" | |
private expectedBody?: Record<string, any> | |
private expectedHeaders: Headers = {} | |
private expectedQuery?: QueryParams | |
private isDoneInternal = false | |
private hasDelayedResponse?: boolean = false | |
constructor(private baseUrl: string) {} | |
get(path: string) { | |
this.method = "get" | |
this.fullUrl = this.baseUrl + path | |
return this | |
} | |
post(path: string, body?: Record<string, any>) { | |
this.method = "post" | |
this.fullUrl = this.baseUrl + path | |
this.expectedBody = body | |
return this | |
} | |
put(path: string, body?: Record<string, any>) { | |
this.method = "put" | |
this.fullUrl = this.baseUrl + path | |
this.expectedBody = body | |
return this | |
} | |
delete(path: string) { | |
this.method = "delete" | |
this.fullUrl = this.baseUrl + path | |
return this | |
} | |
delay(hasDelayedResponse: boolean) { | |
this.hasDelayedResponse = hasDelayedResponse | |
return this | |
} | |
matchHeader(key: string, value: string) { | |
this.expectedHeaders[key.toLowerCase()] = value | |
return this | |
} | |
query(params: QueryParams) { | |
this.expectedQuery = params | |
return this | |
} | |
replyWithError() { | |
this.setupHandler(null, true) | |
return this | |
} | |
reply(status: number, responseBody: any = {}) { | |
this.setupHandler(() => responseBody, false, status) | |
return this | |
} | |
isDone() { | |
return this.isDoneInternal | |
} | |
private setupHandler( | |
getResponseBody: (() => any) | null, | |
isError: boolean, | |
status = 200, | |
) { | |
const handler: HttpHandler = http[this.method]( | |
this.fullUrl, | |
async ({ request }) => { | |
this.isDoneInternal = true | |
const url = new URL(request.url) | |
this.validateHeaders(request) | |
await this.validateJsonBody(request) | |
this.validateQuery(url) | |
if (this.hasDelayedResponse) { | |
await new Promise(resolve => setImmediate(resolve)) | |
} | |
if (isError) { | |
throw new Error("Simulated network error from mswNock.replyWithError") | |
} | |
const body = getResponseBody!() | |
return HttpResponse.json(body, { status }) | |
}, | |
) | |
server.use(handler) | |
} | |
private validateHeaders(request: StrictRequest<any>) { | |
const reqHeaders = Object.fromEntries(request.headers.entries()) | |
for (const [key, value] of Object.entries(this.expectedHeaders)) { | |
if (reqHeaders[key.toLowerCase()] !== value) { | |
this.failJestTest( | |
`Expected header "${key}" to be "${value}", but got "${reqHeaders[key.toLowerCase()]}"`, | |
) | |
} | |
} | |
} | |
private async validateJsonBody(request: StrictRequest<any>) { | |
const contentType = request.headers.get("content-type") ?? "" | |
if (this.expectedBody && contentType.includes("application/json")) { | |
const body = await request.json() | |
for (const [key, expectedValue] of Object.entries(this.expectedBody)) { | |
if (body[key] !== expectedValue) { | |
this.failJestTest( | |
`Expected request body key "${key}" to be "${expectedValue}", but got "${body[key]}"`, | |
) | |
} | |
} | |
} | |
} | |
private validateQuery(url: URL) { | |
if (!this.expectedQuery) return | |
for (const [key, expectedValue] of Object.entries(this.expectedQuery)) { | |
const actual = url.searchParams.get(key) | |
if (actual !== expectedValue) { | |
this.failJestTest( | |
`Expected query param "${key}"="${expectedValue}", but got "${actual}"`, | |
) | |
} | |
} | |
} | |
private failJestTest(message: string) { | |
it(message, () => {}) | |
} | |
} | |
export const mswNock = (baseUrl: string) => new MSWNock(baseUrl) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment