Skip to content
Node.js

Automated Testing for Node.js APIs with Vitest and Supertest

Vitest and Supertest streamline automated testing in Node.js APIs. Learn setup, test creation, and practical workflow integration.

Topic
Node.js
Reading time
5 min
Length
1,128 words
Published
Sep 8, 2026
08:49 pm IST
In this article
  1. Understanding Automated Testing
  2. Types of Automated Tests
  3. Unit Testing
  4. Integration Testing
  5. End-to-End Testing
  6. Setting Up the Project
  7. Installing Vitest and Writing Your First Test
  8. Testing an Express API with Supertest
  9. Practical Steps for Implementing Automated Testing
  10. Limitations and Considerations
  11. Understanding Arrange, Act, Assert

Automated testing is a crucial aspect of maintaining a robust and scalable application. As applications grow, manual testing becomes impractical, leading to potential oversights and bugs. Today, we'll explore how to implement automated testing for a Node.js and TypeScript API using Vitest and Supertest. This approach will help you ensure that your codebase is both reliable and maintainable.

Understanding Automated Testing

Automated testing involves using software tools to execute test cases without manual intervention. This allows developers to quickly verify code functionality and catch any issues that arise from code changes. For instance, testing a simple addition function might look like this:

expect(add(2, 3)).toBe(5);

If the function returns the expected value, the test passes, otherwise, it fails. This process helps maintain the integrity of the codebase by ensuring that new changes do not introduce regressions.

Types of Automated Tests

Automated tests can be broadly categorized into three types: unit tests, integration tests, and end-to-end tests.

Unit Testing

Unit tests focus on testing small, isolated pieces of code. Consider a function that checks for settlement mismatches:

function hasSettlementMismatch(expected: number, actual: number): boolean {
  return expected !== actual;
}
expect(hasSettlementMismatch(50000, 40000)).toBe(true);
expect(hasSettlementMismatch(50000, 50000)).toBe(false);

Here, we verify that the function behaves correctly for matching and non-matching amounts. Unit tests are typically fast to run and help ensure that individual components of the application are functioning as intended.

Integration Testing

Integration testing is the process of testing multiple units or modules together to make sure they interact and work correctly as a group. For example, in an API, a request might pass through a route, controller, service, and database layer. Each part may work correctly on its own, but an integration test can verify that they work correctly when connected.

In this scenario, we ensure that the API correctly processes requests and returns the expected response. Integration tests help catch issues that arise when different parts of the application interact with each other.

End-to-End Testing

End-to-end (E2E) tests simulate real user interactions with the application, ensuring that the entire system functions correctly. While this tutorial focuses on unit and integration testing, tools like Playwright and Cypress are popular for E2E testing. E2E tests are typically more complex and slower to run than unit tests, but they provide a higher level of confidence that the application works as expected in real-world scenarios.

Setting Up the Project

Let's create a Node.js and TypeScript API to use for testing. Begin by setting up a new project:

mkdir testing-api
cd testing-api
npm init -y

Configure the project to use ES modules by adding "type": "module" in package.json. Then, install Express and TypeScript:

npm install express
npm install -D typescript tsx @types/node @types/express
npx tsc --init

Create a simple Express app to test:

import express from "express";
const app = express();
app.use(express.json());
app.get("/health", (_req, res) => {
  res.status(200).json({ status: "ok" });
});
export default app;

Separate the server start logic to avoid running a new server instance during tests:

import app from "./app";
const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

This separation is crucial for testing because it allows the test suite to import the application without starting multiple server instances, which could lead to port conflicts and other issues.

Installing Vitest and Writing Your First Test

Vitest is a testing framework that simplifies writing and running tests. Install it as a development dependency:

npm install -D vitest

Add test scripts to package.json:

{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run"
  }
}

Create a simple addition function and corresponding test:

export function add(a: number, b: number) {
  return a + b;
}

import { expect, test } from "vitest";
import { add } from "./add";
test("adds two numbers", () => {
  expect(add(2, 3)).toBe(5);
});

Run the test suite with npm test to verify everything is working correctly. This setup demonstrates the fundamental structure of a test using Vitest, where you define the expected behavior and check if the actual output matches.

Testing an Express API with Supertest

Supertest allows you to test API endpoints by sending HTTP requests and inspecting responses. Install Supertest and its types:

npm install -D supertest @types/supertest

Create a test for the /health endpoint:

import request from "supertest";
import { expect, test } from "vitest";
import app from "./app";
test("GET /health returns the API status", async () => {
  const response = await request(app).get("/health");
  expect(response.status).toBe(200);
  expect(response.body).toEqual({ status: "ok" });
});

Executing npm test will now run both unit and API tests, ensuring comprehensive coverage. Supertest simplifies the process of testing HTTP endpoints by providing a fluent API for making requests and asserting on responses.

Practical Steps for Implementing Automated Testing

To integrate automated testing into your workflow, follow these steps:

  • Identify critical parts of your application that require testing, such as business logic and API endpoints. Focus on areas that are prone to bugs or have complex interactions.
  • Write tests using the Arrange, Act, Assert pattern to maintain clarity and structure. This pattern involves setting up the necessary conditions for the test (Arrange), executing the code under test (Act), and verifying the results (Assert).
  • Regularly run your test suite during development to catch regressions early. Automated tests should be part of your continuous integration process to ensure that new changes do not introduce bugs.

Limitations and Considerations

While Vitest and Supertest provide a solid foundation for testing, they do not cover every aspect of an application. End-to-end tests, UI testing, and performance tests might require additional tools and frameworks. Also, remember that tests themselves need maintenance and can become a burden if not well-organized.

In my experience, it's crucial to balance the number of tests with their maintenance cost. It's better to have fewer, well-maintained tests that cover critical functionality than a large number of flaky tests that are unreliable.

For a deeper exploration into building resilient applications, you might find the concepts discussed in Building Resilient Full-Stack Apps relevant. Additionally, if you're working in AI, consider our insights on Building a Node.js Chatbot.

Understanding Arrange, Act, Assert

A common way to structure tests is the Arrange, Act, Assert (AAA) pattern. This pattern helps in organizing your test code, making it easier to understand and maintain. Here’s how it works:

  • Arrange: Set up the conditions for your test. This might involve initializing objects, setting up mock data, or configuring the environment in which your test will run.
  • Act: Execute the code under test. This is where you call the function or method that you want to test.
  • Assert: Verify that the outcome of the Act step is as expected. This is done using assertions to compare the actual result with the expected result.

Applying the AAA pattern makes your tests more readable and helps other developers understand the purpose and flow of your tests quickly.

Sources

How to Test a Node.js and TypeScript API with Vitest and Supertest

Every claim above was checked against this source before publishing. The analysis, the code and the opinions are mine.

Frequently asked

What is Vitest?

Vitest is a testing framework powered by Vite, designed to simplify writing and running tests in JavaScript and TypeScript applications.

How does Supertest help in API testing?

Supertest allows you to send HTTP requests to your application during tests, enabling you to test API endpoints without manual intervention.

Why use automated testing in Node.js applications?

Automated testing helps ensure that your code behaves as expected and quickly identifies when changes break existing functionality, thereby improving code reliability and maintainability.

Deepak Kumar

Written by

Deepak Kumar

Sr Software Engineer at India Today Group | Aaj Tak · MERN Stack · Generative AI

Most of my backend work is Node — APIs, queues and ingestion pipelines behind editorial products at India Today Group, plus the services running my own marketplaces. I write here about what those systems actually do once real traffic hits them.

Message me