Select Page
API Testing

Supertest: The Ultimate Guide to Testing Node.js APIs

Learn to test Node.js APIs with Supertest in this guide. Covers setup, endpoint testing, response validation, authentication, mocking, and best practices.

Rajesh K

Senior Testing Engineer

Posted on

09/05/2025

Update on

-- -- --

Next Review on

-- -- --

Supertest Api The Ultimate Guide To Testing Node.js Apis

API testing is crucial for ensuring that your backend services work correctly and reliably. APIs often serve as the backbone of web and mobile applications, so catching bugs early through automated tests can save time and prevent costly issues in production. For Node.js developers and testers, the Supertest API library offers a powerful yet simple way to automate HTTP endpoint testing as part of your workflow. Supertest is a Node.js library (built on the Superagent HTTP client) designed specifically for testing web APIs. It allows you to simulate HTTP requests to your Node.js server and assert the responses without needing to run a browser or a separate client. This means you can test your RESTful endpoints directly in code, making it ideal for integration and end-to-end testing of your server logic. Developers and QA engineers favor Supertest because it is:

  • Lightweight and code-driven – No GUI or separate app required, just JavaScript code.
  • Seamlessly integrated with Node.js frameworks – Works great with Express or any Node HTTP server.
  • Comprehensive – Lets you control headers, authentication, request payloads, and cookies in tests.
  • CI/CD friendly – Easily runs in automated pipelines, returning standard exit codes on test pass/fail.
  • Familiar to JavaScript developers – You write tests in JS/TS, using popular test frameworks like Jest or Mocha, so there’s no context-switching to a new language.

In this guide, we’ll walk through how to set up and use Supertest API for testing various HTTP methods (GET, POST, PUT, DELETE), validate responses (status codes, headers, and bodies), handle authentication, and even mock external API calls. We’ll also discuss how to integrate these tests into CI/CD pipelines and share best practices for effective API testing. By the end, you’ll be confident in writing robust API tests for your Node.js applications using Supertest.

Setting Up Supertest in Your Node.js Project

Before writing tests, you need to add Supertest to your project and set up a testing environment. Assuming you already have a Node.js application (for example, an Express app), follow these steps to get started:

  • Install Supertest (and a test runner): Supertest is typically used with a testing framework like Jest or Mocha. If you don’t have a test runner set up, Jest is a popular choice for beginners due to its zero configuration. Install Supertest and Jest as development dependencies using npm:

    
    npm install --save-dev supertest jest
    
    

    This will add Supertest and Jest to your project’s node_modules. (If you prefer Mocha or another framework, you can install those instead of Jest.)

  • Project Structure: Organize your tests in a dedicated directory. A common convention is to create a folder called tests or to put test files alongside your source files with a .test.js extension. For example:

    
    my-project/
    ├── app.js            # Your Express app or server
    └── tests/
        └── users.test.js # Your Supertest test file
    
    

    In this example, app.js exports an Express application (or Node HTTP server) which the tests will import. The test file users.test.js will contain our Supertest test cases.

  • Configure the Test Script: If you’re using Jest, add a test script to your package.json (if not already present):

    
    "scripts": {
      "test": "jest"
    }
    
    

    This allows you to run all tests with the command npm test. (For Mocha, you might use “test”: “mocha” accordingly.)

With Supertest installed and your project structured for tests, you’re ready to write your first API test.

Writing Your First Supertest API Test

Let’s create a simple test to make sure everything is set up correctly. In your test file (e.g., users.test.js), you’ll require your app and the Supertest library, then define test cases. For example:


const request = require('supertest');    // import Supertest
const app = require('../app');           // import the Express app


describe('GET /api/users', () => {
  it('should return HTTP 200 and a list of users', async () => {
    const res = await request(app).get('/api/users');  // simulate GET request
    expect(res.statusCode).toBe(200);                  // assert status code is 200
    expect(res.body).toBeInstanceOf(Array);            // assert response body is an array
  });
});


In this test, request(app) creates a Supertest client for the Express app. We then call .get(‘/api/users’) and await the response. Finally, we use Jest’s expect to check that the status code is 200 (OK) and that the response body is an array (indicating a list of users).

Now, let’s dive deeper into testing various scenarios and features of an API using Supertest.

Testing Different HTTP Methods (GET, POST, PUT, DELETE)

Real-world APIs use multiple HTTP methods. Supertest makes it easy to test any request method by providing corresponding functions (.get(), .post(), .put(), .delete(), etc.) after calling request(app). Here’s how you can use Supertest for common HTTP methods:


// Examples of testing different HTTP methods with Supertest:

// GET request (fetch list of users)
await request(app)
  .get('/users')
  .expect(200);

// POST request (create a new user with JSON payload)
await request(app)
  .post('/users')
  .send({ name: 'John' })
  .expect(201);

// PUT request (update user with id 1)
await request(app)
  .put('/users/1')
  .send({ name: 'John Updated' })
  .expect(200);

// DELETE request (remove user with id 1)
await request(app)
  .delete('/users/1')
  .expect(204);

In the above snippet, each request is crafted for a specific endpoint and method:

  • GET /users should return 200 OK (perhaps with a list of users).
  • POST /users sends a JSON body ({ name: ‘John’ }) to create a new user. We expect a 201 Created status in response.
  • PUT /users/1 sends an updated name for the user with ID 1 and expects a 200 OK for a successful update.
  • DELETE /users/1 attempts to delete user 1 and expects a 204 No Content (a common response for successful deletions).

Notice the use of .send() for POST and PUT requests – this method attaches a request body. Supertest (via Superagent) automatically sets the Content-Type: application/json header when you pass an object to .send(). You can also chain an .expect(statusCode) to quickly assert the HTTP status.

Sending Data, Headers, and Query Parameters

When testing APIs, you often need to send data or custom headers, or verify endpoints with query parameters. Supertest provides ways to handle all of these:

  • Query Parameters and URL Path Params: Include them in the URL string. For example:

    
    // GET /users?role=admin (query string)
    await request(app).get('/users?role=admin').expect(200);
    
    // GET /users/123 (path parameter)
    await request(app).get('/users/123').expect(200);
    
    

    If your route uses query parameters or dynamic URL segments, constructing the URL in the request call is straightforward.

  • Request Body (JSON or form data): Use .send() for JSON payloads (as shown above). If you need to send form-url-encoded data or file uploads, Supertest (through Superagent) supports methods like .field() and .attach(). However, for most API tests sending JSON via .send({…}) is sufficient. Just ensure your server is configured (e.g., with body-parsing middleware) to handle the content type you send.
  • Custom Headers: Use .set() to set any HTTP header on the request. Common examples include setting an Accept header or authorization tokens. For instance:

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

    Here we set Accept: application/json to tell the server we expect a JSON response, and then we chain an expectation that the Content-Type of the response matches json. You can use .set() for any header your API might require (such as X-API-Key or custom headers).

Setting headers is also how you handle authentication in Supertest, which we’ll cover next.

Handling Authentication and Protected Routes

APIs often have protected endpoints that require authentication, such as a JSON Web Token (JWT) or an API key. To test these, you’ll need to include the appropriate auth credentials in your Supertest requests.

For example, if your API uses a Bearer token in the Authorization header (common with JWT-based auth), you can do:


const token = 'your-jwt-token-here';  // Typically you'd generate or retrieve this in your test setup
await request(app)
  .get('/dashboard')
  .set('Authorization', `Bearer ${token}`)
  .expect(200);

In this snippet, we set the Authorization header before making a GET request to a protected /dashboard route. We then expect a 200 OK if the token is valid and the user is authorized. If the token is missing or incorrect, you could test for a 401 Unauthorized or 403 Forbidden status accordingly.

Tip: In a real test scenario, you might first call a login endpoint (using Supertest) to retrieve a token, then use that token for subsequent requests. You can utilize Jest’s beforeAll hook to obtain auth tokens or set up any required state before running the secured-route tests, and an afterAll to clean up after tests (for example, invalidating a token or closing database connections).

Validating Responses: Status Codes, Bodies, and Headers

Supertest makes it easy to assert various parts of the HTTP response. We’ve already seen using .expect(STATUS) to check status codes, but you can also verify response headers and body content.

You can chain multiple Supertest .expect calls for convenient assertions. For example:


await request(app)
  .get('/users')
  .expect(200)                              // status code is 200
  .expect('Content-Type', /json/)           // Content-Type header contains "json"
  .expect(res => {
    // Custom assertion on response body
    if (!res.body.length) {
      throw new Error('No users found');
    }
  });

Here we chain three expectations:

  • The response status should be 200.
  • The Content-Type header should match a regex /json/ (indicating JSON content).
  • A custom function that throws an error if the res.body array is empty (which would fail the test). This demonstrates how to do more complex assertions on the response body; if the condition inside .expect(res => { … }) is not met, the test will fail with that error.

Alternatively, you can always await the request and use your test framework’s assertion library on the response object. For example, with Jest you could do:


const res = await request(app).get('/users');
expect(res.statusCode).toBe(200);
expect(res.headers['content-type']).toMatch(/json/);
expect(res.body.length).toBeGreaterThan(0);

Both approaches are valid – choose the style you find more readable. Using Supertest’s chaining is concise for simple checks, whereas using your own expect calls on the res object can be more flexible for complex verification.

Testing Error Responses (Negative Testing)

It’s important to test not only the “happy path” but also how your API handles invalid input or error conditions. Supertest can help you simulate error scenarios and ensure your API responds correctly with the right status codes and messages.

For example, if your POST /users endpoint should return a 400 Bad Request when required fields are missing, you can write a test for that case:


it('should return 400 when required fields are missing', async () => {
  const res = await request(app)
    .post('/users')
    .send({});  // sending an empty body, assuming "name" or other fields are required
  expect(res.statusCode).toBe(400);
  // Optionally, check that an error message is returned in the body
  expect(res.body.error).toBeDefined();
});

In this test, we intentionally send an incomplete payload (empty object) to trigger a validation error. We then assert that the response status is 400. You could also assert on the response body (for example, checking that res.body.error or res.body.message contains the expected error info).

Similarly, you might test a 404 Not Found for a GET with a non-existent ID, or 401 Unauthorized when hitting a protected route without credentials. Covering these negative cases ensures your API fails gracefully and returns expected error codes that clients can handle.

Mocking External API Calls in Tests

Sometimes your API endpoints call third-party services (for example, an external REST API). In your tests, you might not want to hit the real external service (to avoid dependencies, flakiness, or side effects). This is where mocking comes in.

For Node.js, a popular library for mocking HTTP requests is Nock. Nock can intercept outgoing HTTP calls and simulate responses, which pairs nicely with Supertest when your code under test makes HTTP requests itself.

To use Nock, install it first:


npm install --save-dev nock

Then, in your tests, you can set up Nock before making the request with Supertest. For example:


// Mock the external API endpoint
nock('https://api.example.com')
  .get('/data')
  .reply(200, { result: 'ok' });


// Now make a request to your app (which calls the external API internally)
const res = await request(app).get('/internal-route');
expect(res.statusCode).toBe(200);
expect(res.body.result).toBe('ok');

In this way, when your application tries to reach api.example.com/data, Nock intercepts the call and returns the fake { result: ‘ok’ }. Our Supertest test then verifies that the app responded as expected without actually calling the real external service.

Best Practices for API Testing with Supertest

To get the most out of Supertest and keep your tests maintainable, consider the following best practices:

  • Separate tests from application code: Keep your test files in a dedicated folder (like tests/) or use a naming convention like *.test.js. This makes it easier to manage code and ensures you don’t accidentally include test code in production builds. It also helps testing frameworks (like Jest) find your tests automatically.
  • Use test data factories or generators: Instead of hardcoding data in your tests, generate dynamic data for more robust testing. For example, use libraries like Faker.js to create random user names, emails, etc. This can reveal issues that only occur with certain inputs and prevents all tests from using the exact same data. It keeps your tests closer to real-world scenarios.
  • Test both success and failure paths: For each API endpoint, write tests for expected successful outcomes (200-range responses) and also for error conditions (4xx/5xx responses). Ensuring you have coverage for edge cases, bad inputs, and unauthorized access will make your API more reliable and bug-resistant.
  • Clean up after tests: Tests should not leave the system in a dirty state. If your tests create or modify data (e.g., adding a user in the database), tear down that data at the end of the test or use setup/teardown hooks (beforeEach, afterEach) to reset state. This prevents tests from interfering with each other. Many testing frameworks allow you to reset database or app state between tests; use those features to isolate test cases.
  • Use environment variables for configuration: Don’t hardcode sensitive values (like API keys, tokens, or database URLs) in your tests. Instead, use environment variables and perhaps a dedicated .env file for your test configuration. By using a package like dotenv, you can load test-specific environment variables (for example, pointing to a test database instead of production). This protects sensitive information and makes it easy to configure tests in different environments (local vs CI, etc.).

By following these practices, you’ll write tests that are cleaner, more reliable, and easier to maintain as your project grows.

Supertest vs Postman vs Rest Assured: Tool Comparison

While Supertest is a great tool for Node.js API testing, you might wonder how it stacks up against other popular API testing solutions like Postman or Rest Assured. Here’s a quick comparison:

S. No Feature Supertest (Node.js) Postman (GUI Tool) Rest Assured (Java)
1 Language/Interface JavaScript (code) GUI + JavaScript (for tests via Newman) Java (code)
2 Testing Style Code-driven; integrated with Jest/Mocha Manual + some automation (collections, Newman CLI) Code-driven (uses JUnit/TestNG)
3 Speed Fast (no UI overhead) Medium (runs through an app or CLI) Fast (runs in JVM)
4 CI/CD Integration Yes (run with npm test) Yes (using Newman CLI in pipelines) Yes (part of build process)
5 Learning Curve Low (if you know JS) Low (easy GUI, scripting possible Medium (requires Java and testing frameworks)
6 Ideal Use Case Node.js projects – embed tests in codebase for TDD/CI Exploratory testing, sharing API collections, quick manual checks Java projects – write integration tests in Java code

In summary, Supertest shines for developers in the Node.js ecosystem who want to write programmatic tests alongside their application code. Postman is excellent for exploring and manually testing APIs (and it can do automation via Newman), but those tests live outside your codebase. Rest Assured is a powerful option for Java developers, but it isn’t applicable for Node.js apps. If you’re working with Node and want seamless integration with your development workflow and CI pipelines, Supertest is likely your best bet for API testing.

Conclusion

Automated API testing is a vital part of modern software development, and Supertest provides Node.js developers and testers with a robust, fast, and intuitive tool to achieve it. By integrating Supertest API tests into your development cycle, you can catch regressions early, ensure each endpoint behaves as intended, and refactor with confidence. We covered how to set up Supertest, write tests for various HTTP methods, handle things like headers, authentication, and external APIs, and even how to incorporate these tests into continuous integration pipelines.

Now it’s time to put this knowledge into practice. Set up Supertest in your Node.js project and start writing some tests for your own APIs. You’ll likely find that the effort pays off with more reliable code and faster debugging when things go wrong. Happy testing!

Frequently Asked Questions

  • What is Supertest API?

    Supertest API (or simply Supertest) is a Node.js library for testing HTTP APIs. It provides a high-level way to send requests to your web server (such as an Express app) and assert the responses. With Supertest, you can simulate GET, POST, PUT, DELETE, and other requests in your test code and verify that your server returns the expected status codes, headers, and data. It's widely used for integration and end-to-end testing of RESTful APIs in Node.js.

  • Can Supertest be used with Jest?

    Yes – Supertest works seamlessly with Jest. In fact, Jest is one of the most popular test runners to use with Supertest. You can write your Supertest calls inside Jest's it() blocks and use Jest’s expect function to make assertions on the response (as shown in the examples above). Jest also provides convenient hooks like beforeAll/afterAll which you can use to set up or tear down test conditions (for example, starting a test database or seeding data) before your Supertest tests run. While we've used Jest for examples here, Supertest is test-runner agnostic, so you could also use it with Mocha, Jasmine, or other frameworks in a similar way.

  • How do I mock APIs when using Supertest?

    You can mock external API calls by using a library like Nock to intercept them. Set up Nock in your test to fake the external service's response, then run your Supertest request as usual. This way, when your application tries to call the external API, Nock responds instead, allowing your test to remain fast and isolated from real external dependencies.

  • How does Supertest compare with Postman for API testing?

    Supertest and Postman serve different purposes. Supertest is a code-based solution — you write JavaScript tests and run them, which is perfect for integration into a development workflow and CI/CD. Postman is a GUI tool great for manually exploring endpoints, debugging, and sharing API collections, with the ability to write tests in the Postman app. You can automate Postman tests using its CLI (Newman), but those tests aren't part of your application's codebase. In contrast, Supertest tests live alongside your code, which means they can be version-controlled and run automatically on every code change. Postman is easier for quick manual checks or for teams that include non-developers, whereas Supertest is better suited for developers who want an automated testing suite integrated with their Node.js project.

Comments(0)

Submit a Comment

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

Talk to our Experts

Amazing clients who
trust us


poloatto
ABB
polaris
ooredo
stryker
mobility