20

In my tests using Supertest and MSW I've noticed that, although they still pass successfully, MSW has started showing warnings for the requests that Supertest is making. For example (see files to reproduce at the end of the post):

$ npm t

> msw-example@1.0.0 test
> jest

 PASS  ./app.test.js
  password API
    ✓ exposes a number of words (76 ms)

  console.warn
    [MSW] Warning: captured a request without a matching request handler:

      • GET http://127.0.0.1:55984/api

    If you still wish to intercept this unhandled request, please create a request handler for it.
    Read more: https://mswjs.io/docs/getting-started/mocks

      at onUnhandledRequest (node_modules/msw/node/lib/index.js:7599:21)
      at node_modules/msw/node/lib/index.js:7630:13
      at fulfilled (node_modules/msw/node/lib/index.js:50:58)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.005 s
Ran all test suites.

The request GET http://127.0.0.1:55984/api is one that Supertest is making to the app, which is the whole point of the test, not one that MSW needs to be handling. These warnings weren't shown when I first wrote the tests, either.

The linked page shows how to create a handler, but I don't want MSW to handle these requests. Why did this start happening, and how can I stop it showing warnings for the "/api" calls?


package.json:

{
  "name": "msw-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.21.1",
    "express": "^4.17.1"
  },
  "devDependencies": {
    "jest": "^27.0.4",
    "msw": "^0.29.0",
    "supertest": "^6.1.3"
  }
}

app.js:

const axios = require("axios");
const express = require("express");

const app = express();

app.get("/api", (_, res) => {
  axios.get("https://api.pwnedpasswords.com/range/ABC12")
    .then(() => res.json({ words: 3 }))
    .catch((err) => res.sendStatus(500));
});

module.exports = app;

app.test.js:

const { rest } = require("msw");
const { setupServer } = require("msw/node");
const request = require("supertest");

const app = require("./app");

const server = setupServer(
  rest.get("https://api.pwnedpasswords.com/range/:range", (req, res, ctx) => {
    return res(ctx.status(200), ctx.text(""));
  }),
);

describe("password API", () => {
  beforeAll(() => server.listen());

  beforeEach(() => server.resetHandlers());

  afterAll(() => server.close());

  it("exposes a number of words", () => {
    return request(app).get("/api").expect(200).then((res) => {
      expect(res.body.words).toBe(3);
    });
  });
});
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437

2 Answers2

35

This feature was introduced in MSW v0.20.0, but in v0.29.0 the default setting for unhandled requests changed from "bypass" to "warn", hence the warnings suddenly appearing in the console. You can reset it to "bypass" as shown in the docs for setupWorker#start or setupServer#listen, in my case:

beforeAll(() => server.listen({ onUnhandledRequest: "bypass" }));

However, this might mean missing warnings for requests you should be handling, so another option is to pass a function that receives the request object. This could e.g. log a warning or throw an error (which will cause the test to fail). In my case, as all of my Supertest requests were to /api endpoints, that looked like:

beforeAll(() => server.listen({ 
  onUnhandledRequest: ({ method, url }) => {
    if (!url.pathname.startsWith("/api")) {
      throw new Error(`Unhandled ${method} request to ${url}`);
    }
  },
}));

As suggested by kettanaito in the comments, I looked into whether you could identify Supertest calls by their headers. Unfortunately it seems like Supertest no longer sets a default User-Agent, so you'd have to do this test-by-test:

describe("password API", () => {
  beforeAll(() => server.listen({ 
    onUnhandledRequest: ({ headers, method, url }) => {
      if (headers.get("User-Agent") !== "supertest") {
        throw new Error(`Unhandled ${method} request to ${url}`);
      }
    },
  }));

  beforeEach(() => server.resetHandlers());

  afterAll(() => server.close());

  it("exposes a number of words", () => {
    return request(app)
      .get("/api")
      .set("User-Agent", "supertest")
      .expect(200)
      .then((res) => {
        expect(res.body.words).toBe(3);
      });
  });
});

From v0.38.0 you can use the second parameter to onUnhandledRequest, conventionally named print, to hand control back to MSW in cases you don't want to handle, e.g.:

beforeAll(() => server.listen({ 
  onUnhandledRequest: ({ headers }, print) => {
    if (headers.get("User-Agent") === "supertest") {
      return;
    }
    print.error();
  },
}));
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • It's worth exploring if Supertest appends any custom request headers that may allow you to determine a request issued by Supertest more reliably. – kettanaito Jun 19 '21 at 17:12
  • @kettanaito great idea! It looks like Superagent [no longer](https://github.com/visionmedia/superagent/issues/1479) sets a default `User-Agent` though, I'd have to do that in all of the tests. – jonrsharpe Jun 19 '21 at 17:21
17

To anyone using a worker and not a server - you can pass an object, with the 'onUnhandledRequest' field set to 'bypass', into the worker's 'start' method, like this:

worker.start({
    onUnhandledRequest: 'bypass',
});

Additional options included 'warn' and 'error' https://mswjs.io/docs/api/setup-worker/start#onunhandledrequest

africola
  • 71
  • 1
  • 7
Elyasaf755
  • 2,239
  • 18
  • 24
  • 2
    This works for Storybook too using `msw-storybook-addon`. Add this object to the `initialize` call like `initialize({ onUnhandledRequest: 'bypass' });` – ness-EE Oct 26 '22 at 15:38