examples icon indicating copy to clipboard operation
examples copied to clipboard

add next.js app router example

Open kettanaito opened this issue 1 year ago • 53 comments
trafficstars

Adds a Next.js 14 (App directory ) + MSW usage example.

Todos

  • [x] https://github.com/vercel/next.js/pull/68193
  • [ ] https://github.com/vercel/next.js/issues/69098

kettanaito avatar Jan 22 '24 17:01 kettanaito

Server-side integration

I got the server-side MSW integration working in Next.js by using the instrumentation hook:

export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { server } = await import('./mocks/node')
    server.listen()
  }
}

This allows MSW to intercept server-side requests Next.js makes.

Downsides

  1. Next seems to evaluate the instrumentation hook once. The import graph it creates will not update if you change mocks/handlers.ts because nothing but the instrumentation hook depends on that import. This means stale mocks until you re-run Next.js/force it re-evaluate the instrumentation hook.

kettanaito avatar Jan 22 '24 17:01 kettanaito

I'm our company we are using this example as a reference.

SalahAdDin avatar Jan 31 '24 16:01 SalahAdDin

@SalahAdDin

I'm our company we are using this example as a reference.

is it working? I've tried to use, but it's showing these messages below:

Internal error: TypeError: fetch failed

`[MSW] Warning: intercepted a request without a matching request handler:

• POST https://telemetry.nextjs.org/api/v1/record`

mizamelo avatar Feb 09 '24 12:02 mizamelo

@SalahAdDin

I'm our company we are using this example as a reference.

Is it working? I've tried to use it, but it's showing these messages below:

Internal error: TypeError: fetch failed

`[MSW] Warning: intercepted a request without a matching request handler:

• POST https://telemetry.nextjs.org/api/v1/record`

Not checked it yet, We just set it up.

SalahAdDin avatar Feb 09 '24 14:02 SalahAdDin

@kettanaito I don't know why but MSW is not intercepting page request. The mock is enabled but does not catch any fetch.

SalahAdDin avatar Mar 05 '24 16:03 SalahAdDin

how does playwright work with this? My test makes the actual api call instead of the mocked call

pandeymangg avatar May 07 '24 07:05 pandeymangg

@pandeymangg, there should be nothing specific to Playwright here. You enable MSW in your Next.js app, then navigate to it in a Playwright test and perform the actions you need.

kettanaito avatar May 08 '24 11:05 kettanaito

Hey @kettanaito what is the status of this example? 😄

Got some of this working in my own app, but curious to see the libraries official recommended approach. Thanks 🙏

PS. love the library, it rivals trpc and react query as my favourite open source projects.

mw999 avatar Jun 13 '24 08:06 mw999

@pandeymangg, the Playwright integration is the same as in this example:

  1. Enable MSW client-side.
  2. Run your app.
  3. Navigate to it in Playwright.

There has never been anything specific to Cypress/Playwright that MSW required.

@mw999, that is a high praise, thank you! No updates on this example, I didn't have time to look into it and, frankly, very little I can do here. If Next.js doesn't pick up changes from the instrumentation hook in HRM, I cannot fix that. I won't be recommending half-baked experiences. For now, you cannot officially use MSW in Next.js.

kettanaito avatar Jun 13 '24 12:06 kettanaito

This is great work, thanks @kettanaito. Have you looked into the work that has been done here? https://github.com/vercel/next.js/tree/canary/packages/next/src/experimental/testmode/playwright

There may be an opportunity to work more closely with the Next.js team on this functionality, especially for use cases like Playwright.

mrmckeb avatar Jun 17 '24 00:06 mrmckeb

One question, with this template, how i can toggle msw with playwright to run only on test env?

bertoldoklinger avatar Jun 28 '24 15:06 bertoldoklinger

@mrmckeb, thank you. Yes, I've heard about the test mode in Next.js. I don't believe it's the right place to integrate MSW. I will look at it again in the future but it's unlikely I will be recommending it.

@bertoldoklinger, you can toggle it by introducing an environment variable. Start the worker conditionally based on the value of that variable.

// your/app.jsx
if (process.env.SOME_VARIABLE === 'some-value') {
  // ...
  worker.start()
}

kettanaito avatar Jul 04 '24 14:07 kettanaito

I'm using this config with vitest, but If I try to run a simple test:

test('should pass', function () {
  expect(true).toBe(true);
});

I get:

Error: No known conditions for "./browser" specifier in "msw" package

Some suggestion to avoid this issue?

crisfcodes avatar Jul 11 '24 03:07 crisfcodes

@crisfcodes, you need to configure your Vitest not to load browser modules in Node.js. Afaik, it doesn't do that by default. Perhaps you've enabled that explicitly?

kettanaito avatar Jul 23 '24 13:07 kettanaito

I'm using jsdom env in Vitest, if that is what you mean with enabled, also I'm wrapping the app with the MockProvider but the worker.start executed conditionally. what I did as workaround is to ignore the file that is throwing the error(browser.ts) with Vitest exclusions.

Also I'm having another issues like:

  1. Infinite browser loading states after manual reload and hot reload/tab switching, it blocks the browser so it's hard to debug, any recommendation for debugging?
  2. Cannot read properties of undefined (reading 'url')](https://github.com/mswjs/msw/issues/2053) from browser/index.js file... I patched the package adding ? to request?.url and that fixed the issue.

MockProvider reference:

import { PropsWithChildren, useEffect, useState } from 'react';
import { env } from '@/constants/env.mjs';

export const MockProvider = ({ children }: PropsWithChildren) => {
  const [mockingEnabled, enableMocking] = useState(false);
  const isProduction = env.NEXT_PUBLIC_ENV === 'prod';
  const isAppReady = mockingEnabled || isProduction;

  useEffect(() => {
    const enableApiMocking = async () => {
      const isLocalDevelopment = env.NEXT_PUBLIC_ENV === 'local';
      const shouldEnableMocking = typeof window !== 'undefined' && isLocalDevelopment;

      if (shouldEnableMocking) {
        const { worker } = await import('../tests/mocks/browser');
        await worker.start({
          serviceWorker: {
            url: `${env.NEXT_PUBLIC_BASE_PATH}/mockServiceWorker.js`,
          },
        });
        enableMocking(true);
      }
    };

    enableApiMocking();
  }, []);

  if (!isAppReady) {
    return null;
  }

  return <>{children}</>;
};

Sorry to use this comment for pointing to other issues, I'm just posting what I've got using this code

crisfcodes avatar Jul 23 '24 22:07 crisfcodes

Does anyone have any example about middleware?

asapo avatar Jul 27 '24 09:07 asapo

FYI the current example doesn't seem to work when using Turbopack on Next 14 or RC.0 of Next 15. However it works great with the exact version in the package.json here.

mikecfisher avatar Jul 30 '24 20:07 mikecfisher

Hi I know that this PR is still in progress - I happen to be investigating how to using MSW with NextJS - it appears that this example as it currently is has the same problem that I've run into - MSW mocks seem to work for the first time the endpoint is called, but for subsequent calls the MSW behaviour is lost.

(I get this error message in subsequent calls):

SERVER LISTEN
is fetch patched? YES
 GET / 200 in 3124ms
is fetch patched? undefined
 ⨯ TypeError: fetch failed
    at async getUser (./app/page.tsx:15:22)
    at async Home (./app/page.tsx:20:18)
digest: "2377642097"
Cause: Error: getaddrinfo ENOTFOUND api.example.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:108:26)
    at GetAddrInfoReqWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'api.example.com'
}
 GET / 200 in 67ms

Is this maybe something to do with how NextJS is munging fetch for its caching?

Update: short answer is YES.

If we replace our getUser with a call using axios

async function getUser() {
  console.log('is fetch patched?', Reflect.get(fetch, '__FOO'))


  const result = await axios.get('https://api.example.com/user');
  const user = result.data as User; 
  return user
}

Then our mocking behaviour works fine.

Is it possible to completely opt out of Next's fetch caching?

dwjohnston avatar Aug 05 '24 04:08 dwjohnston