nuxt / test-utils

🧪 Test utilities for Nuxt
http://nuxt.com/docs/getting-started/testing
MIT License
323 stars 84 forks source link

Unable to use with `msw` to mock requests #775

Closed rinux55 closed 1 month ago

rinux55 commented 8 months ago

Environment


Reproduction

https://stackblitz.com/~/github.com/rinux55/nuxt-fetch

See app.test.ts. In the test/setup.ts and test/server.ts there is an msw server being set up with a mocked endpoint.

Describe the bug

To mock requests made using $fetch and useFetch, I expect to be able to use a utility like msw.

Unfortunately, this doesn't seem to work in combination with @nuxt/test-utils.

I've created a reproduction where you can see that globalThis.fetch is mocked just fine, but useFetch or fetch is not:

https://stackblitz.com/~/github.com/rinux55/nuxt-fetch

Any help with this would be greatly appreciated.

Additional context

No response

Logs

No response

rinux55 commented 8 months ago

Linking https://github.com/unjs/ofetch/issues/295 here as well

ExEr7um commented 7 months ago

As a workaround you can try to use registerEndpoint function.

bcorey85 commented 5 months ago

Just a shot in the dark, but it seems that the addition of vi.resetModules() in @nuxt/test-utils/dist/runtime/entry.mjs is breaking my MSW setup. I'm unable to upgrade past 3.10 due to that change. Commenting out that line makes all of my tests pass as expected.

https://github.com/nuxt/test-utils/compare/v3.10.0...v3.11.0#diff-aed062a1fdd005bf79a0cf1ce9791008fa96eb4b91dbcd9e85d1820b6257450a

Edit: I found a workaround to fix our setup:

    let mockApi

    // Nuxt Vitest environment clears modules after setup, so we're saving a global reference to the first mockApi instance
    if (window.mockApi) {
        mockApi = window.mockApi
    } else {
        mockApi = setupServer()
        window.mockApi = mockApi
    }
david-mears-2 commented 4 months ago

@bcorey85 Where would you add that workaround?

bcorey85 commented 4 months ago

@david-mears-2 It's ultimately in my Vitest setupFiles. I have it wrapped in a function that I call when my test environment spins up:

export const setupMockApi = () => {
    let mockApi

    // Nuxt Vitest environment clears modules after setup, so we're saving a global reference to the first mockApi instance
    if (window.mockApi) {
        mockApi = window.mockApi
    } else {
        mockApi = setupServer()
        window.mockApi = mockApi
    }

    const DEBUG = false

    mockApi.events.on('request:start', ({ request }) => {
        if (DEBUG) {
            console.log('Outgoing:', request.method, request.url)
        }
    })

    // Establish API mocking before all tests.
    beforeAll(() =>
        mockApi.listen({
            // Change this to 'warn' if you need to debug a missing API route.
            // Otherwise leave it on 'bypass' so it doesn't spam warnings when we don't add unnecessary routes
            onUnhandledRequest: DEBUG ? 'warn' : 'bypass'
        })
    )
    // Reset any request handlers that we may add during the tests,
    // so they don't affect other tests.
    afterEach(() => {
        mockApi.resetHandlers()
    })
    // Clean up after the tests are finished.
    afterAll(() => mockApi.close())

    const createMockEndpoint = ({
        path,
        response,
        status = 200,
        method = 'get',
        baseUrl,
        delay,
        showConsoleError = false
    }: MockResponseParams) => {
        const base = baseUrl ?? `${mockApiUrl}/api/1`
        const requestPath = `${base}${path}`

        if (status >= 400 && status <= 599 && !showConsoleError) {
            // Suppress automatic Axios console errors for expected errors
            vi.spyOn(console, 'error').mockImplementation(() => {})
        }

        return mockApi.use(
            http[method](requestPath, async () => {
                if (delay) {
                    await mswDelay(delay)
                }

                return HttpResponse.json(response, { status })
            })
        )
    }

    return {
        mockApi,
        createMockEndpoint
    }
}
shunnNet commented 3 months ago

Hello, I have created a Nuxt module called nuxt-msw that can be used with Nuxt 3 and @nuxt/test-utils to work with msw. You can give it a try and see if it solves your problem.

rinux55 commented 1 month ago

This is now working. You only need to overwrite nuxt's $fetch instance:

import { createFetch } from 'ofetch'

// your msw server
import { server } from './mocks/server'

beforeAll(() => {
  server.listen()
  globalThis.$fetch = createFetch()
})