Search code examples
node.jstestingplaywrightnock

Mocking data with nock in playwright test


I'm writing my first Playwright Screenshot-test for a node application. The application makes serverside api requests and I want to mock the api responses so that my test becomes stable, predictable and can be run offline.

I will use nock to setup mocked data, because I've used nock in my old existing test suite (built on mocha + supertest) with great success.

But in my new Playwright-test I can't get nock to apply to my node app, probably because the node app is started as a separate process.

An old successful test file that uses supertest

const supertest = require("supertest");
const app = setupMyEntireNodeExpressApp();
nock("https://my-api.com").get("/").reply(200, { fakeData:true };
supertest(app).get("/").expect(200).end((err, response) => { doSomething(); }) //let supertest start the node app and make a request

The new playwright-test (myTest.spec.js) where the nock does not apply

const { test, expect } = require("@playwright/test");
const http = require("http");
const app = setupMyEntireNodeExpressApp();
test("My test", async ({ page, context }) => {
  nock("https://my-api.com").get("/").reply(200, { fakeData:true };
  http.createServer(app).listen(); //start node app
  await page.goto("/"); //make a request via playwright
  await expect(page).toHaveScreenshot("myTest.png", { fullPage: true }); //take screenshot via playwright
}

When I run the playwright test, the nock is not being applied to my node app. The app calls https://my-api.com and receives a response from the actual api rather than my mock data.

My GUESS is that the problem is that nock is running within the myTest.spec.js process but createServer starts my app as a separate process.

PS: in my playwright.config.js I've set webServer.reuseExistingServer:false since I'm starting my node app from the test file.

I tried removing http.createServer(app).listen(); and instead using webServer.reuseExistingServer:true and webServer.command: "node app.js". This is a way to let Playwright start the server via the command. But that caused the same problem. In that case I guess the problem is that myTest.spec.js is a separate process while node app.js starts a new process that doesn't have access to the nocks.

With my old successful test I guess the nock works because I can pass my node app object into supertest(app).request so the app is not started as a separate process, but is running within the same process as the test code where the nock is also running.

I wish there was a way to pass my app object to playwright in a similar way, but after reading their docs it doesn't seem like that's an option.

Is there an easy way to solve this?

Perhaps an alternative way of starting my node app using variants of createServer


Solution

  • In short, you cannot. From nock documentation:

    Nock can be used to test modules that perform HTTP requests in isolation.

    What this means is that nock does not actually patch the HTTP server implementation, but it patches the HTTP client instead, and since your HTTP client is whatever the underlying browser Playwright is running, nock has no knowledge of that.

    Therefore, in order to test the API, you will need to use Playwright provided methods, e.g.

    import { test, expect } from '@playwright/test';
    
    test('should create a bug report', async ({ request }) => {
      const newIssue = await request.post('/repos/github-username/test-repo-1/issues', {
        data: {
          title: '[Bug] report 1',
          body: 'Bug description',
        },
      });
    
      expect(newIssue.ok()).toBeTruthy();
    
      const issues = await request.get('/repos/github-username/test-repo-1/issues');
    
      expect(issues.ok()).toBeTruthy();
      expect(await issues.json()).toContainEqual(expect.objectContaining({
        title: '[Bug] report 1',
        body: 'Bug description',
      }));
    });
    

    In this example, we're using the post and get methods provided by the APIRequestContext class to send API requests and validate the server state.

    The next step is to mock the routes themselves:

    await page.route('https://example.com/api/data', (route) => {
      route.fulfill({
        status: 200,
        body: JSON.stringify({ message: 'Hello, World!' }),
      });
    });
    

    Whether the request originates from the APIRequestContext or the application, route will be able to intercept it.

    Unfortunately, this means you cannot leverage nock API that you already familiar with, but it achieves the goal of mocking the API.