From 47fd803b17dbd456514fce3af2da721e29d40071 Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Thu, 20 Jul 2023 15:51:22 +0200 Subject: [PATCH] feat: make life-cycle event listeners have object argument --- MIGRATING.md | 13 +++-- .../start/createFallbackRequestListener.ts | 10 ++-- .../start/createRequestListener.ts | 2 +- .../start/createResponseListener.ts | 21 +++----- src/core/sharedOptions.ts | 52 ++++++++++++++++--- src/core/utils/handleRequest.test.ts | 30 +++++------ src/core/utils/handleRequest.ts | 22 ++++---- src/node/SetupServerApi.ts | 8 +-- .../msw-api/regression/handle-stream.mocks.ts | 4 +- .../life-cycle-events/on.mocks.ts | 14 ++--- .../life-cycle-events/on.node.test.ts | 14 ++--- 11 files changed, 118 insertions(+), 72 deletions(-) diff --git a/MIGRATING.md b/MIGRATING.md index db22c97a5..3c781dacd 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -474,12 +474,19 @@ bypass(request, { ## Life-cycle events +The life-cycle events listeners now accept a single argument being an object with contextual properties. + +```diff +-server.events.on('request:start', (request, requestId) = {}) ++server.events.on('request:start', ({ request, requestId}) => {}) +``` + The request and response instances exposed in the life-cycle API have also been updated to return Fetch API `Request` and `Response` respectively. The request ID is now exposed as a standalone argument (previously, `req.id`). ```js -server.events.on('request:start', (request, requestId) => { +server.events.on('request:start', ({ request, requestId }) => { console.log(request.method, request.url) }) ``` @@ -487,7 +494,7 @@ server.events.on('request:start', (request, requestId) => { To read a request body, make sure to clone the request first. Otherwise, it won't be performed as it would be already read. ```js -server.events.on('request:match', async (request) => { +server.events.on('request:match', async ({ request }) => { // Make sure to clone the request so it could be // processed further down the line. const clone = request.clone() @@ -500,7 +507,7 @@ server.events.on('request:match', async (request) => { The `response:*` events now always contain the response reference, the related request, and its id in the listener arguments. ```js -worker.events.on('response:mocked', (response, request, requestId) => { +worker.events.on('response:mocked', ({ response, request, requestId }) => { console.log('response to %s %s is:', request.method, request.url, response) }) ``` diff --git a/src/browser/setupWorker/start/createFallbackRequestListener.ts b/src/browser/setupWorker/start/createFallbackRequestListener.ts index 31026fdda..93564371b 100644 --- a/src/browser/setupWorker/start/createFallbackRequestListener.ts +++ b/src/browser/setupWorker/start/createFallbackRequestListener.ts @@ -30,7 +30,7 @@ export function createFallbackRequestListener( { onMockedResponse(_, { handler, parsedRequest }) { if (!options.quiet) { - context.emitter.once('response:mocked', (response) => { + context.emitter.once('response:mocked', ({ response }) => { handler.log(requestCloneForLogs, response, parsedRequest) }) } @@ -48,9 +48,11 @@ export function createFallbackRequestListener( ({ response, isMockedResponse, request, requestId }) => { context.emitter.emit( isMockedResponse ? 'response:mocked' : 'response:bypass', - response, - request, - requestId, + { + response, + request, + requestId, + }, ) }, ) diff --git a/src/browser/setupWorker/start/createRequestListener.ts b/src/browser/setupWorker/start/createRequestListener.ts index 2f32d15d9..f82d3fe75 100644 --- a/src/browser/setupWorker/start/createRequestListener.ts +++ b/src/browser/setupWorker/start/createRequestListener.ts @@ -62,7 +62,7 @@ export const createRequestListener = ( ) if (!options.quiet) { - context.emitter.once('response:mocked', (response) => { + context.emitter.once('response:mocked', ({ response }) => { handler.log(requestCloneForLogs, response, parsedRequest) }) } diff --git a/src/browser/setupWorker/start/createResponseListener.ts b/src/browser/setupWorker/start/createResponseListener.ts index ae2784384..7719dfe19 100644 --- a/src/browser/setupWorker/start/createResponseListener.ts +++ b/src/browser/setupWorker/start/createResponseListener.ts @@ -30,24 +30,17 @@ export function createResponseListener(context: SetupWorkerInternalContext) { ? Response.error() : new Response(responseJson.body, responseJson) - if (responseJson.isMockedResponse) { - context.emitter.emit( - 'response:mocked', + context.emitter.emit( + responseJson.isMockedResponse ? 'response:mocked' : 'response:bypass', + { response, /** * @todo @fixme In this context, we don't know anything about * the request. */ - null as any, - responseJson.requestId, - ) - } else { - context.emitter.emit( - 'response:bypass', - response, - null as any, - responseJson.requestId, - ) - } + request: null as any, + requestId: responseJson.requestId, + }, + ) } } diff --git a/src/core/sharedOptions.ts b/src/core/sharedOptions.ts index b5f7af744..ad7f151a2 100644 --- a/src/core/sharedOptions.ts +++ b/src/core/sharedOptions.ts @@ -14,13 +14,51 @@ export interface SharedOptions { } export type LifeCycleEventsMap = { - 'request:start': [request: Request, requestId: string] - 'request:match': [request: Request, requestId: string] - 'request:unhandled': [request: Request, requestId: string] - 'request:end': [request: Request, requestId: string] - 'response:mocked': [response: Response, request: Request, requestId: string] - 'response:bypass': [response: Response, request: Request, requestId: string] - unhandledException: [error: Error, request: Request, requestId: string] + 'request:start': [ + args: { + request: Request + requestId: string + }, + ] + 'request:match': [ + args: { + request: Request + requestId: string + }, + ] + 'request:unhandled': [ + args: { + request: Request + requestId: string + }, + ] + 'request:end': [ + args: { + request: Request + requestId: string + }, + ] + 'response:mocked': [ + args: { + response: Response + request: Request + requestId: string + }, + ] + 'response:bypass': [ + args: { + response: Response + request: Request + requestId: string + }, + ] + unhandledException: [ + args: { + error: Error + request: Request + requestId: string + }, + ] } export type LifeCycleEventEmitter< diff --git a/src/core/utils/handleRequest.test.ts b/src/core/utils/handleRequest.test.ts index 95fa35362..b1dda47bc 100644 --- a/src/core/utils/handleRequest.test.ts +++ b/src/core/utils/handleRequest.test.ts @@ -71,8 +71,8 @@ test('returns undefined for a request with the "x-msw-intention" header equal to expect(result).toBeUndefined() expect(events).toEqual([ - ['request:start', request, requestId], - ['request:end', request, requestId], + ['request:start', { request, requestId }], + ['request:end', { request, requestId }], ]) expect(options.onUnhandledRequest).not.toHaveBeenCalled() expect(callbacks.onPassthroughResponse).toHaveBeenNthCalledWith(1, request) @@ -125,9 +125,9 @@ test('reports request as unhandled when it has no matching request handlers', as expect(result).toBeUndefined() expect(events).toEqual([ - ['request:start', request, requestId], - ['request:unhandled', request, requestId], - ['request:end', request, requestId], + ['request:start', { request, requestId }], + ['request:unhandled', { request, requestId }], + ['request:end', { request, requestId }], ]) expect(options.onUnhandledRequest).toHaveBeenNthCalledWith(1, request, { warning: expect.any(Function), @@ -160,8 +160,8 @@ test('returns undefined on a request handler that returns no response', async () expect(result).toBeUndefined() expect(events).toEqual([ - ['request:start', request, requestId], - ['request:end', request, requestId], + ['request:start', { request, requestId }], + ['request:end', { request, requestId }], ]) expect(options.onUnhandledRequest).not.toHaveBeenCalled() expect(callbacks.onPassthroughResponse).toHaveBeenNthCalledWith(1, request) @@ -205,9 +205,9 @@ test('returns the mocked response for a request with a matching request handler' expect(result).toEqual(mockedResponse) expect(events).toEqual([ - ['request:start', request, requestId], - ['request:match', request, requestId], - ['request:end', request, requestId], + ['request:start', { request, requestId }], + ['request:match', { request, requestId }], + ['request:end', { request, requestId }], ]) expect(callbacks.onPassthroughResponse).not.toHaveBeenCalled() @@ -278,9 +278,9 @@ test('returns a transformed response if the "transformResponse" option is provid ) expect(events).toEqual([ - ['request:start', request, requestId], - ['request:match', request, requestId], - ['request:end', request, requestId], + ['request:start', { request, requestId }], + ['request:match', { request, requestId }], + ['request:end', { request, requestId }], ]) expect(callbacks.onPassthroughResponse).not.toHaveBeenCalled() @@ -336,8 +336,8 @@ it('returns undefined without warning on a passthrough request', async () => { expect(result).toBeUndefined() expect(events).toEqual([ - ['request:start', request, requestId], - ['request:end', request, requestId], + ['request:start', { request, requestId }], + ['request:end', { request, requestId }], ]) expect(options.onUnhandledRequest).not.toHaveBeenCalled() expect(callbacks.onPassthroughResponse).toHaveBeenNthCalledWith(1, request) diff --git a/src/core/utils/handleRequest.ts b/src/core/utils/handleRequest.ts index dae7c7327..f70002179 100644 --- a/src/core/utils/handleRequest.ts +++ b/src/core/utils/handleRequest.ts @@ -43,11 +43,11 @@ export async function handleRequest( emitter: Emitter, handleRequestOptions?: HandleRequestOptions, ): Promise { - emitter.emit('request:start', request, requestId) + emitter.emit('request:start', { request, requestId }) // Perform bypassed requests (i.e. issued via "ctx.fetch") as-is. if (request.headers.get('x-msw-intention') === 'bypass') { - emitter.emit('request:end', request, requestId) + emitter.emit('request:end', { request, requestId }) handleRequestOptions?.onPassthroughResponse?.(request) return } @@ -63,7 +63,11 @@ export async function handleRequest( if (lookupResult.error) { // Allow developers to react to unhandled exceptions in request handlers. - emitter.emit('unhandledException', lookupResult.error, request, requestId) + emitter.emit('unhandledException', { + error: lookupResult.error, + request, + requestId, + }) throw lookupResult.error } @@ -71,8 +75,8 @@ export async function handleRequest( // matching this request. Report the request as unhandled. if (!lookupResult.data) { await onUnhandledRequest(request, handlers, options.onUnhandledRequest) - emitter.emit('request:unhandled', request, requestId) - emitter.emit('request:end', request, requestId) + emitter.emit('request:unhandled', { request, requestId }) + emitter.emit('request:end', { request, requestId }) handleRequestOptions?.onPassthroughResponse?.(request) return } @@ -82,7 +86,7 @@ export async function handleRequest( // When the handled request returned no mocked response, warn the developer, // as it may be an oversight on their part. Perform the request as-is. if (!response) { - emitter.emit('request:end', request, requestId) + emitter.emit('request:end', { request, requestId }) handleRequestOptions?.onPassthroughResponse?.(request) return } @@ -93,7 +97,7 @@ export async function handleRequest( response.status === 302 && response.headers.get('x-msw-intention') === 'passthrough' ) { - emitter.emit('request:end', request, requestId) + emitter.emit('request:end', { request, requestId }) handleRequestOptions?.onPassthroughResponse?.(request) return } @@ -101,7 +105,7 @@ export async function handleRequest( // Store all the received response cookies in the virtual cookie store. readResponseCookies(request, response) - emitter.emit('request:match', request, requestId) + emitter.emit('request:match', { request, requestId }) const requiredLookupResult = lookupResult.data as RequiredDeep @@ -115,7 +119,7 @@ export async function handleRequest( requiredLookupResult, ) - emitter.emit('request:end', request, requestId) + emitter.emit('request:end', { request, requestId }) return transformedResponse } diff --git a/src/node/SetupServerApi.ts b/src/node/SetupServerApi.ts index 46d22f368..a3fee9ade 100644 --- a/src/node/SetupServerApi.ts +++ b/src/node/SetupServerApi.ts @@ -70,9 +70,11 @@ export class SetupServerApi ({ response, isMockedResponse, request, requestId }) => { this.emitter.emit( isMockedResponse ? 'response:mocked' : 'response:bypass', - response, - request, - requestId, + { + response, + request, + requestId, + }, ) }, ) diff --git a/test/browser/msw-api/regression/handle-stream.mocks.ts b/test/browser/msw-api/regression/handle-stream.mocks.ts index b0516a2ef..b3800bfb4 100644 --- a/test/browser/msw-api/regression/handle-stream.mocks.ts +++ b/test/browser/msw-api/regression/handle-stream.mocks.ts @@ -2,8 +2,8 @@ import { setupWorker } from 'msw/browser' const worker = setupWorker() -worker.events.on('response:bypass', async (res) => { - const responseText = await res.text() +worker.events.on('response:bypass', async ({ response }) => { + const responseText = await response.clone().text() console.warn(`[response:bypass] ${responseText}`) }) diff --git a/test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts b/test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts index 95e949189..94c00f446 100644 --- a/test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts +++ b/test/browser/msw-api/setup-worker/life-cycle-events/on.mocks.ts @@ -13,15 +13,15 @@ const worker = setupWorker( }), ) -worker.events.on('request:start', (request, requestId) => { +worker.events.on('request:start', ({ request, requestId }) => { console.warn(`[request:start] ${request.method} ${request.url} ${requestId}`) }) -worker.events.on('request:match', (request, requestId) => { +worker.events.on('request:match', ({ request, requestId }) => { console.warn(`[request:match] ${request.method} ${request.url} ${requestId}`) }) -worker.events.on('request:unhandled', (request, requestId) => { +worker.events.on('request:unhandled', ({ request, requestId }) => { console.warn( `[request:unhandled] ${request.method} ${request.url} ${requestId}`, ) @@ -29,23 +29,23 @@ worker.events.on('request:unhandled', (request, requestId) => { const requestEndListner: ( ...args: LifeCycleEventsMap['request:end'] -) => void = (request, requestId) => { +) => void = ({ request, requestId }) => { console.warn(`[request:end] ${request.method} ${request.url} ${requestId}`) } worker.events.on('request:end', requestEndListner) -worker.events.on('response:mocked', async (response, request, requestId) => { +worker.events.on('response:mocked', async ({ response, requestId }) => { const body = await response.clone().text() console.warn(`[response:mocked] ${body} ${requestId}`) }) -worker.events.on('response:bypass', async (response, request, requestId) => { +worker.events.on('response:bypass', async ({ response, requestId }) => { const body = await response.clone().text() console.warn(`[response:bypass] ${body} ${requestId}`) }) -worker.events.on('unhandledException', (error, request, requestId) => { +worker.events.on('unhandledException', ({ error, request, requestId }) => { console.warn( `[unhandledException] ${request.method} ${request.url} ${requestId} ${error.message}`, ) diff --git a/test/node/msw-api/setup-server/life-cycle-events/on.node.test.ts b/test/node/msw-api/setup-server/life-cycle-events/on.node.test.ts index 9e4bb73b1..316d22ef7 100644 --- a/test/node/msw-api/setup-server/life-cycle-events/on.node.test.ts +++ b/test/node/msw-api/setup-server/life-cycle-events/on.node.test.ts @@ -42,33 +42,33 @@ beforeAll(async () => { ) server.listen() - server.events.on('request:start', (request, requestId) => { + server.events.on('request:start', ({ request, requestId }) => { listener(`[request:start] ${request.method} ${request.url} ${requestId}`) }) - server.events.on('request:match', (request, requestId) => { + server.events.on('request:match', ({ request, requestId }) => { listener(`[request:match] ${request.method} ${request.url} ${requestId}`) }) - server.events.on('request:unhandled', (request, requestId) => { + server.events.on('request:unhandled', ({ request, requestId }) => { listener( `[request:unhandled] ${request.method} ${request.url} ${requestId}`, ) }) - server.events.on('request:end', (request, requestId) => { + server.events.on('request:end', ({ request, requestId }) => { listener(`[request:end] ${request.method} ${request.url} ${requestId}`) }) - server.events.on('response:mocked', async (response, _, requestId) => { + server.events.on('response:mocked', async ({ response, requestId }) => { listener(`[response:mocked] ${await response.text()} ${requestId}`) }) - server.events.on('response:bypass', async (response, _, requestId) => { + server.events.on('response:bypass', async ({ response, requestId }) => { listener(`[response:bypass] ${await response.text()} ${requestId}`) }) - server.events.on('unhandledException', (error, request, requestId) => { + server.events.on('unhandledException', ({ error, request, requestId }) => { listener( `[unhandledException] ${request.method} ${request.url} ${requestId} ${error.message}`, )