Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: remove duplicate response logging in the browser console #1418

Merged
merged 4 commits into from Oct 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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