DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
World desk6 min

Supertest: How to Test Node.js APIs

SuperTest lets you exercise a Node.js app through its HTTP boundary. Learn request and response assertions, async patterns, POST tests, and cookie persistence.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use SuperTest to send HTTP-style requests to your Node.js application and assert the responses it produces: status codes, headers, bodies, or custom conditions. A test runner such as Mocha or Jest organizes and runs the tests; SuperTest provides the request-and-assertion layer. The examples below use Express, but SuperTest accepts an application function or an HTTP server.

Prepare an app that tests can import

Export the application separately from the code that starts the production listener. That lets SuperTest exercise the app without your test having to choose or reserve a fixed port.

// app.js
const express = require('express');
const app = express();

app.use(express.json());

app.get('/user', (req, res) => {
  res.json({ name: 'Ada' });
});

module.exports = app;

Start the listener in a separate entry point when running the application normally:

// server.js
const app = require('./app');

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Listening on ${port}`);
});

When SuperTest receives an app that is not already listening, it binds it to an ephemeral port. You therefore do not need to hard-code a test port for the examples here.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Install SuperTest and make a first request

Add SuperTest as a development dependency. The project README setup uses this installation pattern:

npm install --save-dev supertest

For a basic test, pass the exported app to request(), name the HTTP method and path, then chain the response assertions:

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

test('GET /user returns a user as JSON', async () => {
  await request(app)
    .get('/user')
    .expect('Content-Type', /json/)
    .expect(200)
    .expect({ name: 'Ada' });
});

The example uses a Jest-style test function, but it does not configure Jest or imply that Jest is required. Put the request inside the test function your chosen runner provides. SuperTest’s README also demonstrates Mocha and use without a test framework.

Choose a completion style that fits your test runner

SuperTest supports callback, promise, and async/await patterns. Use one completion style per test so the runner knows when the request and its assertions have finished.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Async/await

Await the request chain. If a status or other .expect() assertion fails, the awaited operation rejects, allowing the test runner to report a failed test.

test('GET /user returns status 200', async () => {
  await request(app)
    .get('/user')
    .expect(200);
});

Promise

Return the request promise from the test. Returning it is important: otherwise a runner may finish the test before the response assertion has completed.

test('GET /user returns status 200', () => {
  return request(app)
    .get('/user')
    .expect(200);
});

Callback with .end()

If you use .end(), pass its error to the test runner’s failure path. This ensures a failed .expect() assertion fails the test instead of being lost in the callback.

it('GET /user returns JSON', (done) => {
  request(app)
    .get('/user')
    .expect('Content-Type', /json/)
    .expect(200)
    .end((err, res) => {
      if (err) return done(err);
      done();
    });
});

SuperTest also supports passing a test-runner callback to an .expect() assertion, as in .expect(200, done). When you chain multiple assertions before .end(), they run in the order declared. Do not call done() before the request finishes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Test a POST request at the HTTP boundary

For a POST route, test the request method, path, payload, and the response contract your application promises. This small in-memory example is runnable without a database; it checks that JSON input is parsed and that the route returns the expected status and body.

// Add to app.js, after app.use(express.json())
app.post('/echo', (req, res) => {
  res.status(201).json({ received: req.body });
});
// test/echo.test.js
const request = require('supertest');
const app = require('../app');

test('POST /echo accepts JSON and returns the received value', async () => {
  await request(app)
    .post('/echo')
    .send({ message: 'hello' })
    .expect('Content-Type', /json/)
    .expect(201)
    .expect({ received: { message: 'hello' } });
});

This verifies the request/response behavior, not persistence or database isolation. For a route backed by storage, arrange test data and cleanup according to your application’s own persistence layer; there is no universal cleanup recipe implied by SuperTest.

Keep cookies between requests with an agent

Independent calls to request(app) are appropriate for independent requests. For a sequence where a cookie from one response must accompany a later request, create an agent with request.agent(app) and use that same agent for both calls.

// Add to app.js
app.get('/start-session', (req, res) => {
  res.cookie('session', 'test-session');
  res.sendStatus(200);
});

app.get('/session', (req, res) => {
  res.json({ cookie: req.get('Cookie') || '' });
});
// test/session.test.js
const request = require('supertest');
const app = require('../app');

test('agent carries a cookie to a later request', async () => {
  const agent = request.agent(app);

  await agent.get('/start-session').expect(200);
  await agent.get('/session')
    .expect(200)
    .expect((res) => {
      if (!res.body.cookie.includes('session=test-session')) {
        throw new Error('Expected session cookie on the second request');
      }
    });
});

The illustrative route above reads the Cookie header to make the carried state observable. In a real app, assert the behavior that matters to the application, such as access to a session-protected route. Keep agent state and test data isolated according to your test suite’s setup.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Other request and assertion choices

  • Response checks: chain expectations for the status, a header, or the response body. Use a custom assertion when the check needs application-specific logic.
  • Request inputs: choose the method and path that exercise the route, and send the payload needed for that case. The POST example sends a JSON object.
  • One request or a sequence: use request(app) for a standalone request and request.agent(app) when state such as cookies must carry across requests.
  • HTTP or HTTP/2: the README includes an explicit HTTP/2 option. Use it only when the server and project requirements call for HTTP/2; ordinary examples use HTTP requests.

Version and compatibility

Repository package metadata retrieved on October 3, 2026 listed SuperTest 7.3.0 and a Node.js requirement of >=14.18.0. Those are time-sensitive package facts, not a guarantee about the version in your project or a permanent compatibility promise. Check the version and engine requirements in your own lockfile and the package metadata before upgrading or setting a Node.js baseline.

Troubleshooting common test failures

  • The test ends before the response arrives: await the request, return its promise, or use the runner’s callback correctly. A callback-style test must call done() only after the request completes.
  • An assertion fails but the callback test passes: forward err from .end((err, res) => ...) to done(err). SuperTest reports failed chained expectations through that callback when using .end().
  • The app cannot be imported in the test: export the application function from its app module and import that module in the test. Keep production listener startup separate so tests can pass the app directly.
  • A later request lacks the expected cookie: use the same request.agent(app) instance for the request that receives the cookie and the request that needs it. Separate calls to request(app) do not express that shared-agent flow.
  • The response body is not parsed as JSON: check that the route actually responds with JSON and the expected content type; then assert the response shape your app returns.
  • A route test depends on stale data: make the test’s setup and cleanup explicit for the app’s own storage. SuperTest exercises the boundary; it does not prescribe database fixtures or cleanup.

Or skip browser setup

SuperTest tests an API’s HTTP contract; it does not replace those assertions with a screenshot. If you also need a rendered-page visual check without setting up browser automation, ScreenshotNeo is a separate option: it captures a page as an image or PDF.

For example, this one-call request captures a page as WebP:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for request options. ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are not billed; and its MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Learn about ScreenshotNeo or sign up for 1,000 free screenshots a month with no card.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. Shenzhen desk3 min
    HONOR Expands Beyond Smartphones With Humanoid Robot RevealHONOR said it unveiled its first humanoid robot at MWC 2026 and named shopping assistance, workplace inspections, and supportive companionship as intended uses. Later Robotics D1 claims and a reported…
  2. Cupertino desk5 min
    Apple Unveils AirPods Max 2: The Upgrade That Should Have Happened Years AgoAirPods Max 2 adds H2-powered audio features and Apple claims up to 1.5Ă— more effective ANC, but its design, Smart Case, and 20-hour battery rating are unchanged. Wired lossless audio…
  3. Cupertino desk4 min
    Apple’s OLED Touch MacBooks Are Coming—but the Dynamic Island Is the Real GambleApple has not announced an OLED touchscreen MacBook, but reports point to high-end models arriving in late 2026 or early 2027. The reported Mac Dynamic Island could be useful, but…
Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.