MSW Passthrough for Requests coming from the client
bradymwilliams opened this issue · 3 comments
I'm using the Epic Stack which features a mocking server (MSW) for external requests. It has a passthrough function that lets the request through but something seems to be happening on the sanity side that doesn't mesh well.
Do we have any examples of using the sanity client with msw or any other mocking services? I simply want to passthrough the request but it seems like sanity does something that prevents the passthrough from working.
http.get(/https:\/\/.*.sanity.io+\/.*/, async ({ request }) => {
const data: any = await fetch(
bypass(new Request(request.url, request)),
I can get the results by manually making the request but you lose built in error handling from the client.
You can mock @sanity/client
with MSW, we use it ourselves internally.
It sounds like the method you use to setup MSW doesn't match the version of @sanity/client
that is used in our test runner.
First, find out which @sanity/client
adapter is used in your testing suite:
import {unstable__adapter} from '@sanity/client'
expect(unstable__adapter).toMatchInlineSnapshot()
Now, if your snapshot is node
, use msw/node
.
Here's a complete example using vitest
:
import {createClient} from '@sanity/client'
import {describe, expect, test} from 'vitest'
const mockClient = createClient({
projectId: 'abc123',
apiVersion: '2023-02-01',
dataset: 'test',
useCdn: false,
})
import {afterAll, afterEach, beforeAll} from 'vitest'
import {setupServer} from 'msw/node'
import {rest} from 'msw'
const authUser = {
"id": "aB1Cd2Ef3",
"name": "Jonhn Doe",
"email": "john@doe.me",
"profileImage": "https://source.unsplash.com/96x96/?face",
"role": "administrator",
"roles": [
{
"name": "administrator",
"title": "Administrator",
"description": "Read and write access to all datasets, with full access to all project settings."
}
],
"provider": "google"
}
export const restHandlers = [
rest.get('https://abc123.api.sanity.io/v2023-02-01/users/me', (req, res, ctx) => {
return res(ctx.status(200), ctx.json(authUser))
}),
]
const server = setupServer(...restHandlers)
// Start server before all tests
beforeAll(() => server.listen({onUnhandledRequest: 'error'}))
// Close server after all tests
afterAll(() => server.close())
// Reset handlers after each test `important for test isolation`
afterEach(() => server.resetHandlers())
describe('fetch users', () => {
test('its able to fetch the authenticated user', async () => {
const result = await mockClient.request({url: '/users/me'})
expect(result).toEqual(authUser)
})
})
If you get xhr
or fetch
in your snapshot, then your test runner is loading the browser version of @sanity/client
, and it's internal get-it
package and that's why MSW doesn't "take".
The simplest way to solve this is to configure your test runner to always load the node version of these libraries:
// Your test-runner.config.ts file
module.exports = {
resolve: {
alias: {
'@sanity/client': require.resolve('@sanity/client/dist/index.js'),
'get-it': require.resolve('get-it/dist/index.js'),
'get-it/middleware': require.resolve('get-it/dist/middleware.js'),
}
}
}
Thanks for the triage @stipsan, however I think the context here is still somehow mismatched. We (meaning, the epic stack) mock all requests by default and passthrough when we don't need it.
import closeWithGrace from 'close-with-grace'
import { passthrough, http } from 'msw'
import { setupServer } from 'msw/node'
import { handlers as githubHandlers } from './github.ts'
import { handlers as resendHandlers } from './resend.ts'
const miscHandlers = [
process.env.REMIX_DEV_ORIGIN
? http.post(`${process.env.REMIX_DEV_ORIGIN}ping`, passthrough)
: null,
].filter(Boolean)
export const server = setupServer(
...miscHandlers,
...resendHandlers,
...githubHandlers,
http.all(/https:\/\/.*.sanity.io+\/.*/, () => {
return passthrough()
}),
)
server.listen({ onUnhandledRequest: 'warn' })
if (process.env.NODE_ENV !== 'test') {
console.info('🔶 Mock server installed')
closeWithGrace(() => {
server.close()
})
}
I've verified the unstable__adaptor in app is node
, but for whatever reason passthrough doesn't work on requests via the sanity client.
It looks like the difference between our internal setups and yours is indeed that we don't use passthrough
, we use specific mocks.
Since unstable__adaptor
reports node
in your case it should work. Maybe the issue is the matcher?
Are you able to write a double test that can demonstrate that it mocks correctly, and fails to passthrough, on a specific request?
import {createClient} from '@sanity/client'
import {describe, expect, test} from 'vitest'
import {afterAll, afterEach, beforeAll} from 'vitest'
import {setupServer} from 'msw/node'
import {rest} from 'msw'
const projectId = 'abc123'
const dataset = 'test'
const realApiVersion = '2023-02-01'
const mockedApiVersion = 'X'
const realClient = createClient({
projectId,
dataset,
apiVersion: realApiVersion,
useCdn: false,
})
const mockClient = createClient({
projectId,
dataset,
apiVersion: mockedApiVersion,
useCdn: false,
})
const authUser = {
"id": "aB1Cd2Ef3",
"name": "Jonhn Doe",
"email": "john@doe.me",
"profileImage": "https://source.unsplash.com/96x96/?face",
"role": "administrator",
"roles": [
{
"name": "administrator",
"title": "Administrator",
"description": "Read and write access to all datasets, with full access to all project settings."
}
],
"provider": "google"
}
export const restHandlers = [
rest.get(`https://${projectId}.api.sanity.io/:apiVersion/users/me`, (req, res, ctx) => {
const { apiVersion } = req.params
if(apiVersion === `v${realApiVersion}`) {
return req.passthrough()
}
return res(ctx.status(200), ctx.json(authUser))
}),
]
const server = setupServer(...restHandlers)
// Start server before all tests
beforeAll(() => server.listen({onUnhandledRequest: 'error'}))
// Close server after all tests
afterAll(() => server.close())
// Reset handlers after each test `important for test isolation`
afterEach(() => server.resetHandlers())
describe('fetch users', () => {
test('its able to fetch the mocked user', async () => {
const result = await mockClient.request({url: '/users/me'})
expect(result).toEqual(authUser)
})
test('its able to attempt fetching the real user', async () => {
const result = await realClient.request({url: '/users/me'})
expect(result).toMatchInlineSnapshot()
})
})