Skip to content

Commit

Permalink
fix: remove duplicate response logging in the browser console (#1418)
Browse files Browse the repository at this point in the history
  • Loading branch information
snaka committed Oct 1, 2022
1 parent 1caf8df commit 78d155f
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 53 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -101,7 +101,7 @@
"outvariant": "^1.3.0",
"path-to-regexp": "^6.2.0",
"statuses": "^2.0.0",
"strict-event-emitter": "^0.2.0",
"strict-event-emitter": "^0.2.6",
"type-fest": "^2.19.0",
"yargs": "^17.3.1"
},
Expand Down
72 changes: 45 additions & 27 deletions src/utils/handleRequest.test.ts
Expand Up @@ -9,17 +9,6 @@ import { response } from '../response'
import { context, MockedRequest } from '..'
import { RequiredDeep } from '../typeUtils'

const emitter = new StrictEventEmitter<ServerLifecycleEventsMap>()
const listener = jest.fn()
function createMockListener(name: string) {
return (...args: any) => {
listener(name, ...args)
}
}
function getEmittedEvents() {
return listener.mock.calls
}

const options: RequiredDeep<SharedOptions> = {
onUnhandledRequest: jest.fn(),
}
Expand All @@ -28,27 +17,44 @@ const callbacks: Partial<Record<keyof HandleRequestOptions<any>, any>> = {
onMockedResponse: jest.fn(),
}

beforeEach(() => {
jest.spyOn(global.console, 'warn').mockImplementation()
function setup() {
const emitter = new StrictEventEmitter<ServerLifecycleEventsMap>()
const listener = jest.fn()

const createMockListener = (name: string) => {
return (...args: any) => {
listener(name, ...args)
}
}

emitter.on('request:start', createMockListener('request:start'))
emitter.on('request:match', createMockListener('request:match'))
emitter.on('request:unhandled', createMockListener('request:unhandled'))
emitter.on('request:end', createMockListener('request:end'))
emitter.on('response:mocked', createMockListener('response:mocked'))
emitter.on('response:bypass', createMockListener('response:bypass'))

const events = listener.mock.calls
return { emitter, events }
}

beforeEach(() => {
jest.spyOn(global.console, 'warn').mockImplementation()
})

afterEach(() => {
jest.resetAllMocks()
emitter.removeAllListeners()
})

test('returns undefined for a request with the "x-msw-bypass" header equal to "true"', async () => {
const { emitter, events } = setup()

const request = new MockedRequest(new URL('http://localhost/user'), {
headers: new Headers({
'x-msw-bypass': 'true',
}),
})
const handlers: RequestHandler[] = []
const handlers: Array<RequestHandler> = []

const result = await handleRequest(
request,
Expand All @@ -59,7 +65,7 @@ test('returns undefined for a request with the "x-msw-bypass" header equal to "t
)

expect(result).toBeUndefined()
expect(getEmittedEvents()).toEqual([
expect(events).toEqual([
['request:start', request],
['request:end', request],
])
Expand All @@ -69,12 +75,14 @@ test('returns undefined for a request with the "x-msw-bypass" header equal to "t
})

test('does not bypass a request with "x-msw-bypass" header set to arbitrary value', async () => {
const { emitter } = setup()

const request = new MockedRequest(new URL('http://localhost/user'), {
headers: new Headers({
'x-msw-bypass': 'anything',
}),
})
const handlers: RequestHandler[] = [
const handlers: Array<RequestHandler> = [
rest.get('/user', (req, res, ctx) => {
return res(ctx.text('hello world'))
}),
Expand All @@ -94,8 +102,10 @@ test('does not bypass a request with "x-msw-bypass" header set to arbitrary valu
})

test('reports request as unhandled when it has no matching request handlers', async () => {
const { emitter, events } = setup()

const request = new MockedRequest(new URL('http://localhost/user'))
const handlers: RequestHandler[] = []
const handlers: Array<RequestHandler> = []

const result = await handleRequest(
request,
Expand All @@ -106,7 +116,7 @@ test('reports request as unhandled when it has no matching request handlers', as
)

expect(result).toBeUndefined()
expect(getEmittedEvents()).toEqual([
expect(events).toEqual([
['request:start', request],
['request:unhandled', request],
['request:end', request],
Expand All @@ -120,8 +130,10 @@ test('reports request as unhandled when it has no matching request handlers', as
})

test('returns undefined and warns on a request handler that returns no response', async () => {
const { emitter, events } = setup()

const request = new MockedRequest(new URL('http://localhost/user'))
const handlers: RequestHandler[] = [
const handlers: Array<RequestHandler> = [
rest.get('/user', () => {
// Intentionally blank response resolver.
return
Expand All @@ -137,7 +149,7 @@ test('returns undefined and warns on a request handler that returns no response'
)

expect(result).toBeUndefined()
expect(getEmittedEvents()).toEqual([
expect(events).toEqual([
['request:start', request],
['request:end', request],
])
Expand All @@ -156,9 +168,11 @@ test('returns undefined and warns on a request handler that returns no response'
})

test('returns the mocked response for a request with a matching request handler', async () => {
const { emitter, events } = setup()

const request = new MockedRequest(new URL('http://localhost/user'))
const mockedResponse = await response(context.json({ firstName: 'John' }))
const handlers: RequestHandler[] = [
const handlers: Array<RequestHandler> = [
rest.get('/user', () => {
return mockedResponse
}),
Expand All @@ -179,7 +193,7 @@ test('returns the mocked response for a request with a matching request handler'
)

expect(result).toEqual(mockedResponse)
expect(getEmittedEvents()).toEqual([
expect(events).toEqual([
['request:start', request],
['request:match', request],
['request:end', request],
Expand All @@ -193,9 +207,11 @@ test('returns the mocked response for a request with a matching request handler'
})

test('returns a transformed response if the "transformResponse" option is provided', async () => {
const { emitter, events } = setup()

const request = new MockedRequest(new URL('http://localhost/user'))
const mockedResponse = await response(context.json({ firstName: 'John' }))
const handlers: RequestHandler[] = [
const handlers: Array<RequestHandler> = [
rest.get('/user', () => {
return mockedResponse
}),
Expand All @@ -217,7 +233,7 @@ test('returns a transformed response if the "transformResponse" option is provid
})

expect(result).toEqual(finalResponse)
expect(getEmittedEvents()).toEqual([
expect(events).toEqual([
['request:start', request],
['request:match', request],
['request:end', request],
Expand All @@ -232,8 +248,10 @@ test('returns a transformed response if the "transformResponse" option is provid
})

it('returns undefined without warning on a passthrough request', async () => {
const { emitter, events } = setup()

const request = new MockedRequest(new URL('http://localhost/user'))
const handlers: RequestHandler[] = [
const handlers: Array<RequestHandler> = [
rest.get('/user', (req) => {
return req.passthrough()
}),
Expand All @@ -248,7 +266,7 @@ it('returns undefined without warning on a passthrough request', async () => {
)

expect(result).toBeUndefined()
expect(getEmittedEvents()).toEqual([
expect(events).toEqual([
['request:start', request],
['request:end', request],
])
Expand Down
46 changes: 46 additions & 0 deletions test/msw-api/setup-worker/response-logging.test.ts
@@ -0,0 +1,46 @@
import { pageWith } from 'page-with'
import { waitFor } from '../../support/waitFor'

function createResponseLogRegexp(username: string): RegExp {
return new RegExp(
`^\\[MSW\\] \\d{2}:\\d{2}:\\d{2} GET https://api\\.github\\.com/users/${username} 200 OK$`,
)
}

test('prints the response info to the console', async () => {
const runtime = await pageWith({
example: require.resolve('../../rest-api/basic.mocks.ts'),
})

const waitForResponseLog = async (exp: RegExp) => {
await waitFor(() => {
expect(runtime.consoleSpy.get('startGroupCollapsed')).toEqual(
expect.arrayContaining([expect.stringMatching(exp)]),
)
})
}

const getResponseLogs = (exp: RegExp) => {
return runtime.consoleSpy.get('startGroupCollapsed').filter((log) => {
return exp.test(log)
})
}

const firstResponseLogRegexp = createResponseLogRegexp('octocat')
await runtime.request('https://api.github.com/users/octocat')
await waitForResponseLog(firstResponseLogRegexp)

// Must print the response summary to the console.
expect(getResponseLogs(firstResponseLogRegexp)).toHaveLength(1)

const secondResopnseLogRegexp = createResponseLogRegexp('john.doe')
await runtime.request('https://api.github.com/users/john.doe')
await waitForResponseLog(secondResopnseLogRegexp)

/**
* Must not duplicate response logs for the current and previous requests.
* @see https://github.com/mswjs/msw/issues/1411
*/
expect(getResponseLogs(secondResopnseLogRegexp)).toHaveLength(1)
expect(getResponseLogs(firstResponseLogRegexp)).toHaveLength(1)
})
38 changes: 20 additions & 18 deletions test/rest-api/body.mocks.ts
Expand Up @@ -16,28 +16,30 @@ const handleRequestBody: ResponseResolver<MockedRequest, RestContext> = (
return res(ctx.json({ body }))
}

const handleMultipartRequestBody: ResponseResolver<MockedRequest, RestContext> =
async (req, res, ctx) => {
const { body } = req

if (typeof body !== 'object') {
throw new Error(
'Expected multipart request body to be parsed but got string',
)
}
const handleMultipartRequestBody: ResponseResolver<
MockedRequest,
RestContext
> = async (req, res, ctx) => {
const { body } = req

const resBody: Record<string, string> = {}
for (const [name, value] of Object.entries(body)) {
if (value instanceof File) {
resBody[name] = await value.text()
} else {
resBody[name] = value as string
}
}
if (typeof body !== 'object') {
throw new Error(
'Expected multipart request body to be parsed but got string',
)
}

return res(ctx.json({ body: resBody }))
const resBody: Record<string, string> = {}
for (const [name, value] of Object.entries(body)) {
if (value instanceof File) {
resBody[name] = await value.text()
} else {
resBody[name] = value as string
}
}

return res(ctx.json({ body: resBody }))
}

const worker = setupWorker(
rest.get('/login', handleRequestBody),
rest.post('/login', handleRequestBody),
Expand Down
14 changes: 7 additions & 7 deletions yarn.lock
Expand Up @@ -9131,20 +9131,20 @@ stream-shift@^1.0.0:
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==

strict-event-emitter@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.0.tgz#78e2f75dc6ea502e5d8a877661065a1e2deedecd"
integrity sha512-zv7K2egoKwkQkZGEaH8m+i2D0XiKzx5jNsiSul6ja2IYFvil10A59Z9Y7PPAAe5OW53dQUf9CfsHKzjZzKkm1w==
dependencies:
events "^3.3.0"

strict-event-emitter@^0.2.4:
version "0.2.4"
resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.4.tgz#365714f0c95f059db31064ca745d5b33e5b30f6e"
integrity sha512-xIqTLS5azUH1djSUsLH9DbP6UnM/nI18vu8d43JigCQEoVsnY+mrlE+qv6kYqs6/1OkMnMIiL6ffedQSZStuoQ==
dependencies:
events "^3.3.0"

strict-event-emitter@^0.2.6:
version "0.2.6"
resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.6.tgz#7bb2022bdabcbf0058cec7118a7bbbfd64367366"
integrity sha512-qDZOqEBoNtKLPb/qAutkXUt7hs3zXgYA1xX4pVa+gZHCZZVLr2r81AzHsK5YrQQhRNphMtkOUyAyOr9e1IxJTw==
dependencies:
events "^3.3.0"

string-argv@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
Expand Down

0 comments on commit 78d155f

Please sign in to comment.