<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael Gwynne</title>
    <description>The latest articles on DEV Community by Michael Gwynne (@votemike).</description>
    <link>https://dev.to/votemike</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1116288%2Fc1af3d1b-feca-42bf-a721-ab8770918900.jpeg</url>
      <title>DEV Community: Michael Gwynne</title>
      <link>https://dev.to/votemike</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/votemike"/>
    <language>en</language>
    <item>
      <title>Server Side Mocking for Playwright in NextJS (App Router) using Mock Service Worker</title>
      <dc:creator>Michael Gwynne</dc:creator>
      <pubDate>Thu, 31 Oct 2024 05:36:18 +0000</pubDate>
      <link>https://dev.to/votemike/server-side-mocking-for-playwright-in-nextjs-app-router-using-mock-service-worker-2p4i</link>
      <guid>https://dev.to/votemike/server-side-mocking-for-playwright-in-nextjs-app-router-using-mock-service-worker-2p4i</guid>
      <description>&lt;p&gt;&lt;a href="https://mswjs.io/" rel="noopener noreferrer"&gt;Mock Service Worker (MSW)&lt;/a&gt; could not mock Server Side calls in &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;NextJS&lt;/a&gt; 14 when using App Router. NextJS 15 has fixed that.&lt;/p&gt;

&lt;p&gt;When testing using Playwright, if the Playwright tests fail, the CI for that branch will fail and disable Github's merging button. Great. 👍&lt;/p&gt;

&lt;p&gt;However, if any of the APIs I rely on are down/broken/misbehaving, this can cause my Playwright tests to fail. And in the case of screenshot testing, updated data in these APIs can again cause the tests to fail.&lt;/p&gt;

&lt;p&gt;Mock Service Worker can help with this&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up a demo app
&lt;/h2&gt;

&lt;p&gt;Create a new NextJS install.&lt;br&gt;
Fetch some data from an API in your page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/app/page.tsx
async function getBulbasaur() {
  const res = await fetch('https://pokeapi.co/api/v2/pokemon/bulbasaur');
  const bulbasaur = await res.json();

  return bulbasaur;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And update your page to use the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/app/page.tsx
export default async function Home() {
  const bulbasaur = await getBulbasaur();
  ...
  &amp;lt;h1&amp;gt;{bulbasaur.name}&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up Playwright
&lt;/h2&gt;

&lt;p&gt;Firstly, let's &lt;a href="https://github.com/votemike/nextjs-app-router-playwright-with-msw/pull/1/files" rel="noopener noreferrer"&gt;set up Playwright&lt;/a&gt;:&lt;br&gt;
Install Playwright and add &lt;code&gt;"test": "playwright test"&lt;/code&gt; to package.json.&lt;br&gt;
Playwright tests a running version of your app. So allow Playwright to start a server that runs your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// playwright.config.ts
module.exports = {
  webServer: {
    command: 'npm run start',
    url: 'http://localhost:3000',
    timeout: 120 * 1000,
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add a test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// tests/index.spec.ts
import { expect, test} from "@playwright/test";

test("Bulbasaur", async ({page}) =&amp;gt; {
  await page.goto(`http://localhost:3000/`);
  const name = await page.innerText('h1');
  expect(name).toBe('bulbasaur');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;npm run build &amp;amp;&amp;amp; npm run test&lt;/code&gt; and you should see your tests pass.&lt;br&gt;
Great.&lt;/p&gt;

&lt;p&gt;However, if this API was down or modified in some way, your test would fail. If you mock the result, you can be confidant that any broken tests are a result of your code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Mock Service Worker
&lt;/h2&gt;

&lt;p&gt;Install MSW via NPM.&lt;br&gt;
Create a set of &lt;a href="https://playwright.dev/docs/test-fixtures" rel="noopener noreferrer"&gt;Playwright fixtures&lt;/a&gt; for the server side mocking:&lt;br&gt;
(Note that you have to set the &lt;code&gt;__prerender_bypass&lt;/code&gt; cookie to avoid testing a statically generated version of your page)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// tests/fixture.ts
import {Page, test as base} from "@playwright/test";
import {createServer, Server} from "http";
import {AddressInfo} from "net";
import next from "next";
import path from "path";
import {parse} from "url";
import * as json from "../.next/prerender-manifest.json";
import {setupServer, SetupServerApi} from "msw/node";

export const test = base.extend&amp;lt;{ dynamicPage: Page, port: string, requestInterceptor: SetupServerApi }&amp;gt;({
  dynamicPage: async ({context}, use) =&amp;gt; {
    await context.addCookies([{
      name: '__prerender_bypass',
      value: json.preview.previewModeId,
      domain: 'localhost',
      path: '/'
    }]);

    const dynamicPage = await context.newPage();
    await use(dynamicPage);
  },
  port: [
    async ({}, use) =&amp;gt; {
      const app = next({dev: false, dir: path.resolve(__dirname, "..")});
      await app.prepare();

      const handle = app.getRequestHandler();

      const server: Server = await new Promise(resolve =&amp;gt; {
        const server = createServer((req, res) =&amp;gt; {
          const parsedUrl = parse(req.url, true);
          handle(req, res, parsedUrl);
        });

        server.listen((error) =&amp;gt; {
          if (error) throw error;
          resolve(server);
        });
      });
      const port = String((server.address() as AddressInfo).port);
      await use(port);
    },
    {
      scope: "worker",
      auto: true
    }
  ],
  requestInterceptor: [
    async ({}, use) =&amp;gt; {
      await use((() =&amp;gt; {
        const requestInterceptor = setupServer();

        requestInterceptor.listen({
          onUnhandledRequest: "bypass"
        });

        return requestInterceptor
      })());
    },
    {
      scope: "worker"
    }
  ]
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update your test file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// tests/index.spec.ts
import {expect} from "@playwright/test";
import {http, HttpResponse} from "msw";
import {test} from './fixture';

test("Bulbasaur", async ({dynamicPage, port, requestInterceptor}) =&amp;gt; {
  requestInterceptor.use(http.get('https://pokeapi.co/api/v2/pokemon/bulbasaur', () =&amp;gt; {
    return HttpResponse.json({name: 'squirtle'})
  }));

  await dynamicPage.goto(`http://localhost:${port}/`);
  const name = await dynamicPage.innerText('h1');
  expect(name).toBe('squirtle');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that we have mocked the response of the name of "bulbasaur" to be "squirtle" just to prove that we are getting the mocked result.&lt;br&gt;
Since the "port" fixture is creating a new server for our app to run on, we can also delete our previously created playwright.config.ts.&lt;br&gt;
Run &lt;code&gt;npm run build &amp;amp;&amp;amp; npm run test&lt;/code&gt; again and you should see your tests pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what is happening here?
&lt;/h2&gt;

&lt;p&gt;Before we are running our tests, we are building our production-like app. The "port" fixture is then creating it's own server to run our app on. We can then use the &lt;code&gt;requestInterceptor&lt;/code&gt; to intercept calls to certain URLs that the aforementioned server is making and mock their responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;GraphQL queries should be able to be handled in the same way, mocking using &lt;code&gt;graphql.query&lt;/code&gt; instead of &lt;code&gt;http.get&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What else?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/votemike/nextjs-app-router-playwright-with-msw/pulls" rel="noopener noreferrer"&gt;View the complete code for Playwright, MSW or both into a vanilla NextJS repo&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;In my example, the endpoint is mocked in each test. However, you could extract your mocking out into separate functions or files depending on your use-case.&lt;/li&gt;
&lt;li&gt;See how to do this in pages router at my old article - &lt;a href="https://dev.to/votemike/server-side-mocking-for-playwright-in-nextjs-using-mock-service-worker-4p6g"&gt;https://dev.to/votemike/server-side-mocking-for-playwright-in-nextjs-using-mock-service-worker-4p6g&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>msw</category>
      <category>playwright</category>
      <category>mocking</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Server Side Mocking for Playwright in NextJS using Mock Service Worker</title>
      <dc:creator>Michael Gwynne</dc:creator>
      <pubDate>Tue, 11 Jul 2023 16:46:27 +0000</pubDate>
      <link>https://dev.to/votemike/server-side-mocking-for-playwright-in-nextjs-using-mock-service-worker-4p6g</link>
      <guid>https://dev.to/votemike/server-side-mocking-for-playwright-in-nextjs-using-mock-service-worker-4p6g</guid>
      <description>&lt;p&gt;After cycling through several technologies, I settled on &lt;a href="https://playwright.dev/"&gt;Playwright&lt;/a&gt; for the e2e testing of my &lt;a href="https://nextjs.org/"&gt;NextJS&lt;/a&gt; app.&lt;/p&gt;

&lt;p&gt;If the Playwright tests fail, the CI for that branch will fail and disable Github's merging button. Great. 👍&lt;/p&gt;

&lt;p&gt;However, if any of the APIs I rely on are down/broken/misbehaving, this can cause my Playwright tests to fail. And in the case of screenshot testing, updated data in these APIs can again cause the tests to fail.&lt;/p&gt;

&lt;p&gt;"Aha!" I thought, "Mock Service Worker can help with this". However, all documentation and articles I found were aimed towards client-side request mocking rather than server side.&lt;br&gt;
Luckily I stumbled across a &lt;a href="https://frontend-digest.com/using-playwright-to-test-next-js-applications-80a767540091"&gt;very useful article by Dominik Ferber&lt;/a&gt; which, after some trial and error, allowed me to mock my server-side requests.&lt;br&gt;
While the article really helped me, I feel it was overly complex for my use-case, slightly out-of-date and missed some key points I found myself which I think could be beneficial to others.&lt;/p&gt;
&lt;h2&gt;
  
  
  Set up a demo app
&lt;/h2&gt;

&lt;p&gt;Create a new NextJS install.&lt;br&gt;
Fetch some data from an API in your page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/pages/index.tsx
export async function getServerSideProps() {
  const res = await fetch('https://pokeapi.co/api/v2/pokemon/bulbasaur');
  const bulbasaur = await res.json();

  return {
    props: {
      bulbasaur
    }
  };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And update your page to use the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/pages/index.tsx
export default function Home({bulbasaur}: {bulbasaur: {name: string}}) {
  ...
  &amp;lt;h1&amp;gt;{bulbasaur.name}&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up Playwright
&lt;/h2&gt;

&lt;p&gt;Firstly, let's &lt;a href="https://github.com/votemike/nextjs-playwright-with-msw/pull/1/files"&gt;set up Playwright&lt;/a&gt;:&lt;br&gt;
Install Playwright and add &lt;code&gt;"test": "playwright test"&lt;/code&gt; to package.json.&lt;br&gt;
Playwright tests a running version of your app. So allow Playwright to start a server that runs your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// playwright.config.ts
module.exports = {
  webServer: {
    command: 'npm run start',
    url: 'http://localhost:3000',
    timeout: 120 * 1000,
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add a test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// tests/index.spec.ts
import { expect, test} from "@playwright/test";

test("Bulbasaur", async ({page}) =&amp;gt; {
  await page.goto(`http://localhost:3000/`);
  const name = await page.innerText('h1');
  expect(name).toBe('bulbasaur');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;npm run build &amp;amp;&amp;amp; npm run test&lt;/code&gt; and you should see your tests pass.&lt;br&gt;
Great.&lt;/p&gt;

&lt;p&gt;However, if this API was down or modified in some way, your test would fail. If you mock the result, you can be confidant that any broken tests are a result of your code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Mock Service Worker
&lt;/h2&gt;

&lt;p&gt;Install MSW via NPM.&lt;br&gt;
Create a set of &lt;a href="https://playwright.dev/docs/test-fixtures"&gt;Playwright fixtures&lt;/a&gt; for the server side mocking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// tests/fixture.ts
import {test as base} from "@playwright/test";
import {createServer, Server} from "http";
import {rest} from "msw";
import type {SetupServerApi} from "msw/node";
import {AddressInfo} from "net";
import next from "next";
import path from "path";
import {parse} from "url";

const test = base.extend&amp;lt;{ port: string; requestInterceptor: SetupServerApi; rest: typeof rest; }&amp;gt;({
  port: [
    async ({}, use) =&amp;gt; {
      const app = next({dev: false, dir: path.resolve(__dirname, "..")});
      await app.prepare();

      const handle = app.getRequestHandler();

      const server: Server = await new Promise(resolve =&amp;gt; {
        const server = createServer((req, res) =&amp;gt; {
          const parsedUrl = parse(req.url, true);
          handle(req, res, parsedUrl);
        });

        server.listen((error) =&amp;gt; {
          if (error) throw error;
          resolve(server);
        });
      });
      const port = String((server.address() as AddressInfo).port);
      await use(port);
    },
    {
      scope: "worker",
      auto: true
    }
  ],
  requestInterceptor: [
    async({}, use) =&amp;gt; {
      await use((() =&amp;gt; {
        const {setupServer} = require("msw/node");
        const requestInterceptor = setupServer();

        requestInterceptor.listen({
          onUnhandledRequest: "bypass"
        });

        return requestInterceptor
      })());
    },
    {
      scope: "worker"
    }
  ],
  rest
});

export default test;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update your test file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// tests/index.spec.ts
import {expect} from "@playwright/test";
import test from "./fixture";

test("Bulbasaur", async ({page, port, rest, requestInterceptor}) =&amp;gt; {
  requestInterceptor.use(
    rest.get('https://pokeapi.co/api/v2/pokemon/bulbasaur', (req, res, ctx) =&amp;gt;
      res(
        ctx.json({name: 'squirtle'})
      )
    )
  );
  await page.goto(`http://localhost:${port}/`);
  const name = await page.innerText('h1');
  expect(name).toBe('squirtle');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that we have mocked the response of the name of "bulbasaur" to be "squirtle" just to prove that we are getting the mocked result.&lt;br&gt;
Since the "port" fixture is creating a new server for our app to run on, we can also delete our previously created playwright.config.ts.&lt;br&gt;
Run &lt;code&gt;npm run build &amp;amp;&amp;amp; npm run test&lt;/code&gt; again and you should see your tests pass.&lt;/p&gt;
&lt;h2&gt;
  
  
  So what is happening here?
&lt;/h2&gt;

&lt;p&gt;Before we are running our tests, we are building our production-like app. The "port" fixture is then creating it's own server to run our app on. We can then use the &lt;code&gt;requestInterceptor&lt;/code&gt; to intercept calls to certain URLs that the aforementioned server is making and mock their responses.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;In my case, I had a customised server setup which ran during &lt;code&gt;npm run start&lt;/code&gt;. I extracted all the important bits in to a separate file so that I could call them both as part of &lt;code&gt;npm run start&lt;/code&gt; and in the "port" fixture.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// tests/fixture.ts
const server: Server = await new Promise(resolve =&amp;gt; {
  const server = setUpMyServer(handle);

  server.listen((error) =&amp;gt; {
    if (error) throw error;
    resolve(server);
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What else?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/votemike/nextjs-playwright-with-msw/pulls"&gt;View the complete code for Playwright, MSW or both into a vanilla NextJS repo&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;In my example, the endpoint is mocked in each test. However, you could extract your mocking out into separate functions or files depending on your use-case.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://frontend-digest.com/using-playwright-to-test-next-js-applications-80a767540091"&gt;Dominik Ferber's article&lt;/a&gt; also creates a build of the app automatically before running tests, mentions client side testing using MSW and talks about build time requests.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>msw</category>
      <category>playwright</category>
      <category>mocking</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
