mrkeyoor.com_
Sun 20 Sept 11:43 UTC
npmTestingupdated 20 Sept 2026

supertest review

supertest 7.2.2 drives a Node HTTP server through SuperAgent and adds chainable assertions for status, headers, bodies, and cookies. Pass an Express-style app function or an `http.Server`; if it is not listening, supertest binds an ephemeral port for the request. It also accepts a real base URL for black-box checks, supports persistent cookie agents, multipart uploads, redirects, authentication, and HTTP/2. Version 7.2 introduced cookie assertion helpers, then 7.2.1 fixed a case-sensitive module path and 7.2.2 replaced the cookie helper's `should` dependency with native assertions.

Verdict

supertest 7.2.2 installed in 1.9 seconds and occupied 6 MB across 44 packages in our sandbox with 0 audit findings, but it supplied no TypeScript types and could not build for browsers. Install it for Node HTTP boundary tests; skip it for browser execution, lightweight handler injection, or typed tests that cannot accept DefinitelyTyped lag.

We installed it

Lab card: what happened when we installed supertestScreenshot of supertest documentation
Install✓ · 1.9s44 packages on disk · 6 MB
ImportESM import works · require() works · CommonJS package
Browsern/acould not be bundled for the browser (Node-only code, most likely)
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does supertest install cleanly?

Yes. In a fresh container with an empty cache, npm install supertest finished in 2 seconds, leaving 44 packages and 6 MB on disk. npm audit reported no known vulnerabilities.

Can supertest run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does supertest work with both ESM and CommonJS?

Yes. Both import 'supertest' and require('supertest') worked in Node 22 in our run. The package is published as CommonJS.

Does supertest include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

supertest or light-my-request: which should you use?

light-my-request: Use it for fast in-process injection against Fastify-style handlers when a real socket is unnecessary. supertest 7.2.2 installed in 1.9 seconds and occupied 6 MB across 44 packages in our sandbox with 0 audit findings, but it supplied no TypeScript types and could not build for browsers.

When should you not use supertest?

Tests run in a browser, edge worker, or service worker. Our browser bundle failed because supertest depends on Node server and networking APIs.

API stability4/5The `request(app).get().send().expect()` chain has stayed recognizable for years and remains a thin extension of SuperAgent. Version 7.2 adds cookie assertion helpers without disturbing ordinary request tests, while 7.2.1 and 7.2.2 repair their packaging. The package has no exports map or bundled types, so module-resolution and DefinitelyTyped changes sit outside the documented runtime API.
Docs4/5The README covers app and server inputs, ephemeral ports, promises, callbacks, custom assertions, authentication, multipart data, persistent agents, HTTP/2, response expectations, and the cookie helper API. Examples are executable and identify the `.end()` error trap. The documentation is one long page, mixes older callback style with async code, and does not provide an official TypeScript section.
Maintenance4/5GitHub shows 14,396 stars, 190 combined open issues and pull requests, an unarchived repository, and a push on 2 April 2026. Release 7.2.2 shipped on 6 January 2026 after two same-day fixes to the new cookie assertion code. Releases and dependency updates continue, though the large open queue and lag between the latest package and repository push leave more triage surface than smaller test utilities.
Ecosystem5/5npm counted 17,332,804 downloads for 19 through 25 August 2026. Supertest works with common Node frameworks because it accepts the standard server callback shape, and its SuperAgent base handles JSON, forms, files, auth, redirects, cookies, and HTTP/2. Jest, Mocha, Vitest, and Node test can all await it. TypeScript support depends on a separate package.

Use it if

  • Node API tests should exercise the real HTTP boundary without managing a fixed test port.
  • Status, JSON, header, redirect, cookie, multipart, or authentication behavior belongs in one readable request chain.
  • A login flow needs a cookie jar shared across several requests through `request.agent(app)`.
  • The same test style should cover an in-process app in unit runs and a deployed HTTP URL in smoke tests.
Skip it if

Setup reality

We installed supertest 7.2.2 in a fresh Node 22 Bookworm sandbox in 1.9 seconds. The install left 44 packages and used 6 MB on disk. npm audit reported 0 known vulnerabilities at every severity. The package itself declares 3 direct dependencies, 0 peer dependencies, and 76 KB unpacked under MIT. It is CommonJS with no exports map; require() and ESM import both worked. No TypeScript declarations were found.

There are no credentials or config files for in-process tests. Pass the app callback to request(app) and supertest opens an ephemeral listener if needed. That still uses a real local socket, so close database clients, queues, timers, and any server you started yourself in test teardown. A base URL tests a live service instead; credentials, seed data, and cleanup then belong to that environment and should never be assumed isolated.

A chain runs when awaited, returned, given a callback, or ended with .end(). With .end(), failed expectations arrive as err; the callback must pass that error to the test runner. Promise and async forms are harder to mishandle. Add .expect(404) or the intended error status, because an undeclared non-2xx response is treated as an error by the SuperAgent layer. Expectation callbacks execute in registration order and may normalize the response before later assertions.

Cookie state survives only through request.agent(), not separate request(app) calls. Multipart fixtures are read from the filesystem, and relative paths depend on the process working directory. HTTP/2 is opt-in with { http2: true }. The package is Node-only: our esbuild browser target could not resolve its server-oriented code. TypeScript users need @types/supertest, and those community types can trail newly added helpers such as the version 7.2 cookie assertions.

Patterns

Check status and JSON together assert-json-response

const request = require('supertest');
const app = require('../app');

it('returns one user', async () => {
  await request(app)
    .get('/user')
    .expect('Content-Type', /json/)
    .expect(200, { name: 'john' });
});

An object body uses deep equality after parsing, and the 2 expectations run in the order shown.

Await and inspect selected fields inspect-response

const response = await request(app)
  .get('/users')
  .set('Accept', 'application/json')
  .expect(200);

expect(response.body).toEqual([{ id: 1 }]);

Awaiting starts the request; declaring the 200 status makes an unexpected error response fail at the HTTP boundary.

Send a JSON request body post-json

await request(app)
  .post('/users')
  .send({ name: 'john' })
  .set('Accept', 'application/json')
  .expect(201);

Passing an object to `.send()` selects JSON serialization; a pre-encoded string follows different content-type handling.

Declare an expected 404 test-error-response

const response = await request(app)
  .get('/users/missing')
  .expect('Content-Type', /json/)
  .expect(404);

expect(response.body.code).toBe('USER_NOT_FOUND');

SuperAgent treats an undeclared 4xx response as an error, so put the expected 404 in the chain.

Reuse cookies after login persist-login-cookie

const agent = request.agent(app);

await agent
  .post('/login')
  .send({ username: 'ann', password: 'secret' })
  .expect(204);
await agent.get('/dashboard').expect(200);

Use the same 1 agent for the flow; separate `request(app)` calls create separate cookie jars.

Send a bearer token set-authorization

await request(app)
  .get('/me')
  .set('Authorization', `Bearer ${token}`)
  .expect(200);

Setting the header directly works even when the separately published TypeScript declarations lag a SuperAgent auth helper.

Check a redirect without following it assert-redirect

await request(app)
  .get('/old-path')
  .expect(302)
  .expect('Location', '/new-path');

Redirect following is off by default; use `.redirects(1)` only when the final 200 response is the behavior under test.

Upload fields and a fixture upload-multipart

const path = require('node:path');

await request(app)
  .post('/avatar')
  .field('name', 'profile image')
  .attach('avatar', path.join(__dirname, 'fixtures', 'avatar.png'))
  .expect(201);

An absolute fixture path keeps the test stable when 1 runner changes the process working directory.

Validate a response shape write-custom-assertion

function hasPageLinks(response) {
  if (!('next' in response.body)) throw new Error('missing next');
  if (!('previous' in response.body)) throw new Error('missing previous');
}

await request(app)
  .get('/items')
  .expect(200)
  .expect(hasPageLinks);

A custom expectation fails by throwing; its return value is ignored by the assertion chain.

Remove volatile fields before comparison normalize-before-assertion

await request(app)
  .post('/users')
  .send({ name: 'John' })
  .expect((response) => {
    delete response.body.createdAt;
    response.body.name = response.body.name.toLowerCase();
  })
  .expect(201, { id: 1, name: 'john' });

Expectations execute in order, so the first function changes the body seen by the later exact assertion.

Exercise an HTTP/2 app enable-http2

await request(app, { http2: true })
  .get('/health')
  .expect(200, { ok: true });

HTTP/2 is opt-in through the second argument; the default path uses ordinary Node HTTP/1.1.

Return callback failures to Mocha propagate-callback-error

it('creates a user', (done) => {
  request(app)
    .post('/users')
    .send({ name: 'john' })
    .expect(201)
    .end((error) => done(error));
});

`.end()` sends failed expectations through its first argument; dropping that 1 error can make a broken test pass or time out.

Alternatives

PackageRegistryPick it when
light-my-requestnpmUse it for fast in-process injection against Fastify-style handlers when a real socket is unnecessary.
undicinpmUse it for standards-based HTTP calls to an already running service and bring assertions from the test runner.
chai-httpnpmUse it when Chai assertions are already the suite's central style and its request plugin fits the runner.
axiosnpmUse it for general HTTP client calls when app binding, cookie agents, and response assertion chains are unnecessary.

More testing guides

pytest · chai · vitest · jsdom · playwright · coverage · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.