Skip to content

Commit 450e7bc

Browse files
authoredFeb 12, 2024··
feat: support concurrent test runs via "server.boundary" (#2000)
1 parent f805d34 commit 450e7bc

15 files changed

+439
-139
lines changed
 

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"check:exports": "node \"./config/scripts/validate-esm.js\"",
5353
"test": "pnpm test:unit && pnpm test:node && pnpm test:browser && pnpm test:native",
5454
"test:unit": "vitest",
55-
"test:node": "vitest --config=./test/node/vitest.config.ts",
55+
"test:node": "vitest run --config=./test/node/vitest.config.ts",
5656
"test:native": "vitest --config=./test/native/vitest.config.ts",
5757
"test:browser": "playwright test -c ./test/browser/playwright.config.ts",
5858
"test:modules:node": "vitest --config=./test/modules/node/vitest.config.ts",

‎src/browser/setupWorker/glossary.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export interface SetupWorkerInternalContext {
102102
startOptions: RequiredDeep<StartOptions>
103103
worker: ServiceWorker | null
104104
registration: ServiceWorkerRegistration | null
105-
requestHandlers: Array<RequestHandler>
105+
getRequestHandlers(): Array<RequestHandler>
106106
requests: Map<string, Request>
107107
emitter: Emitter<LifeCycleEventsMap>
108108
keepAliveInterval?: number

‎src/browser/setupWorker/setupWorker.ts

+3-11
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ export class SetupWorkerApi
5858
isMockingEnabled: false,
5959
startOptions: null as any,
6060
worker: null,
61+
getRequestHandlers: () => {
62+
return this.handlersController.currentHandlers()
63+
},
6164
registration: null,
62-
requestHandlers: this.currentHandlers,
6365
requests: new Map(),
6466
emitter: this.emitter,
6567
workerChannel: {
@@ -151,16 +153,6 @@ export class SetupWorkerApi
151153
},
152154
}
153155

154-
/**
155-
* @todo Not sure I like this but "this.currentHandlers"
156-
* updates never bubble to "this.context.requestHandlers".
157-
*/
158-
Object.defineProperties(context, {
159-
requestHandlers: {
160-
get: () => this.currentHandlers,
161-
},
162-
})
163-
164156
this.startHandler = context.supports.serviceWorkerApi
165157
? createFallbackStart(context)
166158
: createStartHandler(context)

‎src/browser/setupWorker/start/createFallbackRequestListener.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function createFallbackRequestListener(
2424
const response = await handleRequest(
2525
request,
2626
requestId,
27-
context.requestHandlers,
27+
context.getRequestHandlers(),
2828
options,
2929
context.emitter,
3030
{

‎src/browser/setupWorker/start/createRequestListener.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const createRequestListener = (
4343
await handleRequest(
4444
request,
4545
requestId,
46-
context.requestHandlers,
46+
context.getRequestHandlers(),
4747
options,
4848
context.emitter,
4949
{

‎src/core/SetupApi.ts

+33-9
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,38 @@ import { pipeEvents } from './utils/internal/pipeEvents'
1010
import { toReadonlyArray } from './utils/internal/toReadonlyArray'
1111
import { Disposable } from './utils/internal/Disposable'
1212

13+
export abstract class HandlersController {
14+
abstract prepend(runtimeHandlers: Array<RequestHandler>): void
15+
abstract reset(nextHandles: Array<RequestHandler>): void
16+
abstract currentHandlers(): Array<RequestHandler>
17+
}
18+
19+
export class InMemoryHandlersController implements HandlersController {
20+
private handlers: Array<RequestHandler>
21+
22+
constructor(private initialHandlers: Array<RequestHandler>) {
23+
this.handlers = [...initialHandlers]
24+
}
25+
26+
public prepend(runtimeHandles: Array<RequestHandler>): void {
27+
this.handlers.unshift(...runtimeHandles)
28+
}
29+
30+
public reset(nextHandlers: Array<RequestHandler>): void {
31+
this.handlers =
32+
nextHandlers.length > 0 ? [...nextHandlers] : [...this.initialHandlers]
33+
}
34+
35+
public currentHandlers(): Array<RequestHandler> {
36+
return this.handlers
37+
}
38+
}
39+
1340
/**
1441
* Generic class for the mock API setup.
1542
*/
1643
export abstract class SetupApi<EventsMap extends EventMap> extends Disposable {
17-
protected initialHandlers: ReadonlyArray<RequestHandler>
18-
protected currentHandlers: Array<RequestHandler>
44+
protected handlersController: HandlersController
1945
protected readonly emitter: Emitter<EventsMap>
2046
protected readonly publicEmitter: Emitter<EventsMap>
2147

@@ -31,8 +57,7 @@ export abstract class SetupApi<EventsMap extends EventMap> extends Disposable {
3157
),
3258
)
3359

34-
this.initialHandlers = toReadonlyArray(initialHandlers)
35-
this.currentHandlers = [...initialHandlers]
60+
this.handlersController = new InMemoryHandlersController(initialHandlers)
3661

3762
this.emitter = new Emitter<EventsMap>()
3863
this.publicEmitter = new Emitter<EventsMap>()
@@ -59,24 +84,23 @@ export abstract class SetupApi<EventsMap extends EventMap> extends Disposable {
5984
),
6085
)
6186

62-
this.currentHandlers.unshift(...runtimeHandlers)
87+
this.handlersController.prepend(runtimeHandlers)
6388
}
6489

6590
public restoreHandlers(): void {
66-
this.currentHandlers.forEach((handler) => {
91+
this.handlersController.currentHandlers().forEach((handler) => {
6792
handler.isUsed = false
6893
})
6994
}
7095

7196
public resetHandlers(...nextHandlers: Array<RequestHandler>): void {
72-
this.currentHandlers =
73-
nextHandlers.length > 0 ? [...nextHandlers] : [...this.initialHandlers]
97+
this.handlersController.reset(nextHandlers)
7498
}
7599

76100
public listHandlers(): ReadonlyArray<
77101
RequestHandler<RequestHandlerDefaultInfo, any, any>
78102
> {
79-
return toReadonlyArray(this.currentHandlers)
103+
return toReadonlyArray(this.handlersController.currentHandlers())
80104
}
81105

82106
private createLifeCycleEvents(): LifeCycleEventEmitter<EventsMap> {

‎src/native/index.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { FetchInterceptor } from '@mswjs/interceptors/fetch'
22
import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/XMLHttpRequest'
3-
import { RequestHandler } from '~/core/handlers/RequestHandler'
4-
import { SetupServerApi } from '../node/SetupServerApi'
3+
import type { RequestHandler } from '~/core/handlers/RequestHandler'
4+
import { SetupServerCommonApi } from '../node/SetupServerCommonApi'
55

66
/**
77
* Sets up a requests interception in React Native with the given request handlers.
@@ -11,11 +11,11 @@ import { SetupServerApi } from '../node/SetupServerApi'
1111
*/
1212
export function setupServer(
1313
...handlers: Array<RequestHandler>
14-
): SetupServerApi {
14+
): SetupServerCommonApi {
1515
// Provision request interception via patching the `XMLHttpRequest` class only
1616
// in React Native. There is no `http`/`https` modules in that environment.
17-
return new SetupServerApi(
17+
return new SetupServerCommonApi(
1818
[FetchInterceptor, XMLHttpRequestInterceptor],
19-
...handlers,
19+
handlers,
2020
)
2121
}

‎src/node/SetupServerApi.ts

+64-95
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,82 @@
1-
import {
2-
BatchInterceptor,
3-
HttpRequestEventMap,
4-
Interceptor,
5-
InterceptorReadyState,
6-
} from '@mswjs/interceptors'
7-
import { invariant } from 'outvariant'
8-
import { SetupApi } from '~/core/SetupApi'
9-
import { RequestHandler } from '~/core/handlers/RequestHandler'
10-
import { LifeCycleEventsMap, SharedOptions } from '~/core/sharedOptions'
11-
import { RequiredDeep } from '~/core/typeUtils'
12-
import { handleRequest } from '~/core/utils/handleRequest'
13-
import { devUtils } from '~/core/utils/internal/devUtils'
14-
import { mergeRight } from '~/core/utils/internal/mergeRight'
15-
import { SetupServer } from './glossary'
1+
import { AsyncLocalStorage } from 'node:async_hooks'
2+
import { ClientRequestInterceptor } from '@mswjs/interceptors/ClientRequest'
3+
import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/XMLHttpRequest'
4+
import { FetchInterceptor } from '@mswjs/interceptors/fetch'
5+
import { HandlersController } from '~/core/SetupApi'
6+
import type { RequestHandler } from '~/core/handlers/RequestHandler'
7+
import type { SetupServer } from './glossary'
8+
import { SetupServerCommonApi } from './SetupServerCommonApi'
169

17-
const DEFAULT_LISTEN_OPTIONS: RequiredDeep<SharedOptions> = {
18-
onUnhandledRequest: 'warn',
19-
}
20-
21-
export class SetupServerApi
22-
extends SetupApi<LifeCycleEventsMap>
23-
implements SetupServer
24-
{
25-
protected readonly interceptor: BatchInterceptor<
26-
Array<Interceptor<HttpRequestEventMap>>,
27-
HttpRequestEventMap
28-
>
29-
private resolvedOptions: RequiredDeep<SharedOptions>
10+
const store = new AsyncLocalStorage<RequestHandlersContext>()
3011

31-
constructor(
32-
interceptors: Array<{
33-
new (): Interceptor<HttpRequestEventMap>
34-
}>,
35-
...handlers: Array<RequestHandler>
36-
) {
37-
super(...handlers)
12+
type RequestHandlersContext = {
13+
initialHandlers: Array<RequestHandler>
14+
handlers: Array<RequestHandler>
15+
}
3816

39-
this.interceptor = new BatchInterceptor({
40-
name: 'setup-server',
41-
interceptors: interceptors.map((Interceptor) => new Interceptor()),
42-
})
43-
this.resolvedOptions = {} as RequiredDeep<SharedOptions>
17+
/**
18+
* A handlers controller that utilizes `AsyncLocalStorage` in Node.js
19+
* to prevent the request handlers list from being a shared state
20+
* across mutliple tests.
21+
*/
22+
class AsyncHandlersController implements HandlersController {
23+
private rootContext: RequestHandlersContext
4424

45-
this.init()
25+
constructor(initialHandlers: Array<RequestHandler>) {
26+
this.rootContext = { initialHandlers, handlers: [] }
4627
}
4728

48-
/**
49-
* Subscribe to all requests that are using the interceptor object
50-
*/
51-
private init(): void {
52-
this.interceptor.on('request', async ({ request, requestId }) => {
53-
const response = await handleRequest(
54-
request,
55-
requestId,
56-
this.currentHandlers,
57-
this.resolvedOptions,
58-
this.emitter,
59-
)
60-
61-
if (response) {
62-
request.respondWith(response)
63-
}
29+
get context(): RequestHandlersContext {
30+
return store.getStore() || this.rootContext
31+
}
6432

65-
return
66-
})
33+
public prepend(runtimeHandlers: Array<RequestHandler>) {
34+
this.context.handlers.unshift(...runtimeHandlers)
35+
}
6736

68-
this.interceptor.on(
69-
'response',
70-
({ response, isMockedResponse, request, requestId }) => {
71-
this.emitter.emit(
72-
isMockedResponse ? 'response:mocked' : 'response:bypass',
73-
{
74-
response,
75-
request,
76-
requestId,
77-
},
78-
)
79-
},
80-
)
37+
public reset(nextHandlers: Array<RequestHandler>) {
38+
const context = this.context
39+
context.handlers = []
40+
context.initialHandlers =
41+
nextHandlers.length > 0 ? nextHandlers : context.initialHandlers
8142
}
8243

83-
public listen(options: Partial<SharedOptions> = {}): void {
84-
this.resolvedOptions = mergeRight(
85-
DEFAULT_LISTEN_OPTIONS,
86-
options,
87-
) as RequiredDeep<SharedOptions>
44+
public currentHandlers(): Array<RequestHandler> {
45+
const { initialHandlers, handlers } = this.context
46+
return handlers.concat(initialHandlers)
47+
}
48+
}
8849

89-
// Apply the interceptor when starting the server.
90-
this.interceptor.apply()
50+
export class SetupServerApi
51+
extends SetupServerCommonApi
52+
implements SetupServer
53+
{
54+
constructor(handlers: Array<RequestHandler>) {
55+
super(
56+
[ClientRequestInterceptor, XMLHttpRequestInterceptor, FetchInterceptor],
57+
handlers,
58+
)
9159

92-
this.subscriptions.push(() => {
93-
this.interceptor.dispose()
94-
})
60+
this.handlersController = new AsyncHandlersController(handlers)
61+
}
9562

96-
// Assert that the interceptor has been applied successfully.
97-
// Also guards us from forgetting to call "interceptor.apply()"
98-
// as a part of the "listen" method.
99-
invariant(
100-
[InterceptorReadyState.APPLYING, InterceptorReadyState.APPLIED].includes(
101-
this.interceptor.readyState,
102-
),
103-
devUtils.formatMessage(
104-
'Failed to start "setupServer": the interceptor failed to apply. This is likely an issue with the library and you should report it at "%s".',
105-
),
106-
'https://github.com/mswjs/msw/issues/new/choose',
107-
)
63+
public boundary<Fn extends (...args: Array<any>) => unknown>(
64+
callback: Fn,
65+
): (...args: Parameters<Fn>) => ReturnType<Fn> {
66+
return (...args: Parameters<Fn>): ReturnType<Fn> => {
67+
return store.run<any, any>(
68+
{
69+
initialHandlers: this.handlersController.currentHandlers(),
70+
handlers: [],
71+
},
72+
callback,
73+
...args,
74+
)
75+
}
10876
}
10977

11078
public close(): void {
111-
this.dispose()
79+
super.close()
80+
store.disable()
11281
}
11382
}

‎src/node/SetupServerCommonApi.ts

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* @note This API is extended by both "msw/node" and "msw/native"
3+
* so be minding about the things you import!
4+
*/
5+
import type { RequiredDeep } from 'type-fest'
6+
import { invariant } from 'outvariant'
7+
import {
8+
BatchInterceptor,
9+
InterceptorReadyState,
10+
type HttpRequestEventMap,
11+
type Interceptor,
12+
} from '@mswjs/interceptors'
13+
import type { LifeCycleEventsMap, SharedOptions } from '~/core/sharedOptions'
14+
import { SetupApi } from '~/core/SetupApi'
15+
import { handleRequest } from '~/core/utils/handleRequest'
16+
import type { RequestHandler } from '~/core/handlers/RequestHandler'
17+
import { mergeRight } from '~/core/utils/internal/mergeRight'
18+
import { devUtils } from '~/core/utils/internal/devUtils'
19+
import type { SetupServerCommon } from './glossary'
20+
21+
export const DEFAULT_LISTEN_OPTIONS: RequiredDeep<SharedOptions> = {
22+
onUnhandledRequest: 'warn',
23+
}
24+
25+
export class SetupServerCommonApi
26+
extends SetupApi<LifeCycleEventsMap>
27+
implements SetupServerCommon
28+
{
29+
protected readonly interceptor: BatchInterceptor<
30+
Array<Interceptor<HttpRequestEventMap>>,
31+
HttpRequestEventMap
32+
>
33+
private resolvedOptions: RequiredDeep<SharedOptions>
34+
35+
constructor(
36+
interceptors: Array<{ new (): Interceptor<HttpRequestEventMap> }>,
37+
handlers: Array<RequestHandler>,
38+
) {
39+
super(...handlers)
40+
41+
this.interceptor = new BatchInterceptor({
42+
name: 'setup-server',
43+
interceptors: interceptors.map((Interceptor) => new Interceptor()),
44+
})
45+
46+
this.resolvedOptions = {} as RequiredDeep<SharedOptions>
47+
48+
this.init()
49+
}
50+
51+
/**
52+
* Subscribe to all requests that are using the interceptor object
53+
*/
54+
private init(): void {
55+
this.interceptor.on('request', async ({ request, requestId }) => {
56+
const response = await handleRequest(
57+
request,
58+
requestId,
59+
this.handlersController.currentHandlers(),
60+
this.resolvedOptions,
61+
this.emitter,
62+
)
63+
64+
if (response) {
65+
request.respondWith(response)
66+
}
67+
68+
return
69+
})
70+
71+
this.interceptor.on(
72+
'response',
73+
({ response, isMockedResponse, request, requestId }) => {
74+
this.emitter.emit(
75+
isMockedResponse ? 'response:mocked' : 'response:bypass',
76+
{
77+
response,
78+
request,
79+
requestId,
80+
},
81+
)
82+
},
83+
)
84+
}
85+
86+
public listen(options: Partial<SharedOptions> = {}): void {
87+
this.resolvedOptions = mergeRight(
88+
DEFAULT_LISTEN_OPTIONS,
89+
options,
90+
) as RequiredDeep<SharedOptions>
91+
92+
// Apply the interceptor when starting the server.
93+
this.interceptor.apply()
94+
95+
this.subscriptions.push(() => {
96+
this.interceptor.dispose()
97+
})
98+
99+
// Assert that the interceptor has been applied successfully.
100+
// Also guards us from forgetting to call "interceptor.apply()"
101+
// as a part of the "listen" method.
102+
invariant(
103+
[InterceptorReadyState.APPLYING, InterceptorReadyState.APPLIED].includes(
104+
this.interceptor.readyState,
105+
),
106+
devUtils.formatMessage(
107+
'Failed to start "setupServer": the interceptor failed to apply. This is likely an issue with the library and you should report it at "%s".',
108+
),
109+
'https://github.com/mswjs/msw/issues/new/choose',
110+
)
111+
}
112+
113+
public close(): void {
114+
this.dispose()
115+
}
116+
}

‎src/node/glossary.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import type { PartialDeep } from 'type-fest'
2-
import {
2+
import type {
33
RequestHandler,
44
RequestHandlerDefaultInfo,
55
} from '~/core/handlers/RequestHandler'
6-
import {
6+
import type {
77
LifeCycleEventEmitter,
88
LifeCycleEventsMap,
99
SharedOptions,
1010
} from '~/core/sharedOptions'
1111

12-
export interface SetupServer {
12+
export interface SetupServerCommon {
1313
/**
1414
* Starts requests interception based on the previously provided request handlers.
1515
*
@@ -60,3 +60,17 @@ export interface SetupServer {
6060
*/
6161
events: LifeCycleEventEmitter<LifeCycleEventsMap>
6262
}
63+
64+
export interface SetupServer extends SetupServerCommon {
65+
/**
66+
* Wraps the given function in a boundary. Any changes to the
67+
* network behavior (e.g. adding runtime request handlers via
68+
* `server.use()`) will be scoped to this boundary only.
69+
* @param callback A function to run (e.g. a test)
70+
*
71+
* @see {@link https://mswjs.io/docs/api/setup-server/boundary `server.boundary()` API reference}
72+
*/
73+
boundary<Fn extends (...args: Array<any>) => unknown>(
74+
callback: Fn,
75+
): (...args: Parameters<Fn>) => ReturnType<Fn>
76+
}

‎src/node/setupServer.ts

+3-10
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
import { ClientRequestInterceptor } from '@mswjs/interceptors/ClientRequest'
2-
import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/XMLHttpRequest'
3-
import { FetchInterceptor } from '@mswjs/interceptors/fetch'
4-
import { RequestHandler } from '~/core/handlers/RequestHandler'
1+
import type { RequestHandler } from '~/core/handlers/RequestHandler'
52
import { SetupServerApi } from './SetupServerApi'
6-
import { SetupServer } from './glossary'
73

84
/**
95
* Sets up a requests interception in Node.js with the given request handlers.
@@ -13,9 +9,6 @@ import { SetupServer } from './glossary'
139
*/
1410
export const setupServer = (
1511
...handlers: Array<RequestHandler>
16-
): SetupServer => {
17-
return new SetupServerApi(
18-
[ClientRequestInterceptor, XMLHttpRequestInterceptor, FetchInterceptor],
19-
...handlers,
20-
)
12+
): SetupServerApi => {
13+
return new SetupServerApi(handlers)
2114
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { setupServer } from 'msw/node'
5+
6+
const server = setupServer()
7+
8+
beforeAll(() => {
9+
server.listen()
10+
})
11+
12+
afterAll(() => {
13+
server.close()
14+
})
15+
16+
it.concurrent('forwards arguments to the callback function', async () => {
17+
server.boundary((...args) => {
18+
expect(args).toEqual([1, { 2: true }, [3]])
19+
})(1, { 2: true }, [3])
20+
})
21+
22+
it.concurrent('returns the result of the callback function', async () => {
23+
const result = server.boundary((number: number) => {
24+
return number * 10
25+
})(2)
26+
27+
expect(result).toBe(20)
28+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { http, HttpResponse } from 'msw'
5+
import { setupServer } from 'msw/node'
6+
7+
const server = setupServer(
8+
http.get('/initial', () => {
9+
return HttpResponse.text('initial')
10+
}),
11+
)
12+
13+
beforeAll(() => {
14+
server.listen()
15+
})
16+
17+
afterAll(() => {
18+
server.close()
19+
})
20+
21+
describe.concurrent('concurrent tests', () => {
22+
it(
23+
'resolves request against the initial handlers',
24+
server.boundary(async () => {
25+
const response = await fetch('/initial')
26+
expect(response.status).toBe(200)
27+
expect(await response.text()).toBe('initial')
28+
}),
29+
)
30+
31+
it(
32+
'resolves request against the in-test handler override',
33+
server.boundary(async () => {
34+
server.use(
35+
http.get('/initial', () => {
36+
return HttpResponse.text('override')
37+
}),
38+
)
39+
40+
const response = await fetch('/initial')
41+
expect(response.status).toBe(200)
42+
expect(await response.text()).toBe('override')
43+
}),
44+
)
45+
46+
it(
47+
'resolves requests against the initial handlers again',
48+
server.boundary(async () => {
49+
const response = await fetch('/initial')
50+
expect(response.status).toBe(200)
51+
expect(await response.text()).toBe('initial')
52+
}),
53+
)
54+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { HttpResponse, http } from 'msw'
5+
import { setupServer } from 'msw/node'
6+
7+
const server = setupServer(
8+
http.get('https://example.com', () => {
9+
return HttpResponse.json({ name: 'John' })
10+
}),
11+
)
12+
13+
beforeAll(() => {
14+
server.listen()
15+
})
16+
17+
afterAll(() => {
18+
server.close()
19+
})
20+
21+
it.concurrent(
22+
'treats higher scope handlers as initial handlers',
23+
server.boundary(async () => {
24+
expect(
25+
await await fetch('https://example.com').then((response) =>
26+
response.json(),
27+
),
28+
).toEqual({ name: 'John' })
29+
30+
server.use(
31+
http.get('https://example.com', () => {
32+
return HttpResponse.json({ override: true })
33+
}),
34+
)
35+
expect(
36+
await await fetch('https://example.com').then((response) =>
37+
response.json(),
38+
),
39+
).toEqual({ override: true })
40+
}),
41+
)
42+
43+
it.concurrent(
44+
'resets the runtime handlers to the initial handlers',
45+
server.boundary(async () => {
46+
server.use(
47+
http.get('https://example.com', () => {
48+
return HttpResponse.json({ override: true })
49+
}),
50+
)
51+
expect(
52+
await await fetch('https://example.com').then((response) =>
53+
response.json(),
54+
),
55+
).toEqual({ override: true })
56+
57+
server.resetHandlers()
58+
59+
expect(
60+
await await fetch('https://example.com').then((response) =>
61+
response.json(),
62+
),
63+
).toEqual({ name: 'John' })
64+
}),
65+
)
66+
67+
it.concurrent(
68+
'treats the higher boundary handlers as initial handlers for nested boundary',
69+
server.boundary(async () => {
70+
server.use(
71+
http.get('https://example.com', () => {
72+
return HttpResponse.json({ override: true })
73+
}),
74+
)
75+
76+
await server.boundary(async () => {
77+
expect(
78+
await await fetch('https://example.com').then((response) =>
79+
response.json(),
80+
),
81+
).toEqual({ override: true })
82+
83+
server.resetHandlers()
84+
85+
// Reset does nothing at this point because no runtime
86+
// request handlers were added within this boundary.
87+
expect(
88+
await await fetch('https://example.com').then((response) =>
89+
response.json(),
90+
),
91+
).toEqual({ override: true })
92+
93+
server.use(
94+
http.get('https://example.com', () => {
95+
return HttpResponse.json({ nested: true })
96+
}),
97+
)
98+
99+
expect(
100+
await await fetch('https://example.com').then((response) =>
101+
response.json(),
102+
),
103+
).toEqual({ nested: true })
104+
})
105+
}),
106+
)

‎test/node/msw-api/setup-server/life-cycle-events/on.node.test.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ beforeAll(async () => {
6363
'response:mocked',
6464
async ({ response, request, requestId }) => {
6565
listener(
66-
`[response:mocked] ${await response.text()} ${request.method} ${request.url} ${requestId}`,
66+
`[response:mocked] ${await response.text()} ${request.method} ${
67+
request.url
68+
} ${requestId}`,
6769
)
6870
},
6971
)
@@ -72,7 +74,9 @@ beforeAll(async () => {
7274
'response:bypass',
7375
async ({ response, request, requestId }) => {
7476
listener(
75-
`[response:bypass] ${await response.text()} ${request.method} ${request.url} ${requestId}`,
77+
`[response:bypass] ${await response.text()} ${request.method} ${
78+
request.url
79+
} ${requestId}`,
7680
)
7781
},
7882
)

0 commit comments

Comments
 (0)
Please sign in to comment.