# How to run backend unit tests in Replit

- Tool: Replit
- Difficulty: Advanced
- Time required: 25-35 minutes
- Compatibility: All Replit plans. Works with Node.js (Jest, Mocha) and Python (pytest, unittest). Examples use Jest for Express.js and pytest for Flask.
- Last updated: March 2026

## TL;DR

Set up backend unit tests in Replit by installing a testing framework (Jest for Node.js or pytest for Python), writing test files for your API endpoints, and configuring a test script in package.json or .replit. Run tests from Shell with 'npm test' or 'pytest'. Structure your tests to cover route handlers, database operations, and error cases. Configure the .replit file to run tests before deployment.

## Write and Run Backend API Unit Tests in Replit

This tutorial shows you how to set up a testing framework, write meaningful unit tests for your backend API, and run them inside Replit. You will install Jest (for Node.js) or pytest (for Python), write tests that validate API endpoints, mock external dependencies, and configure your project so tests run reliably. This guide is for developers who want to catch bugs before they reach production.

## Before you start

- A Replit account (any plan)
- A working backend API project (Express.js or Flask)
- Basic understanding of HTTP methods and REST API patterns
- Familiarity with the Replit Shell tab
- No prior testing experience required

## Step-by-step guide

### 1. Install your testing framework

Open the Shell tab in your Replit workspace (Tools > Shell). For Node.js/Express.js projects, install Jest and supertest (for HTTP request testing). For Python/Flask projects, install pytest and requests. These are the most widely used testing frameworks for their respective languages and have extensive documentation and community support.

```
# For Node.js (Express.js) projects:
npm install --save-dev jest supertest

# For Python (Flask) projects:
pip install pytest requests
```

**Expected result:** The testing packages install successfully. For Node.js, jest and supertest appear in package.json under devDependencies. For Python, pytest appears in pip list output.

### 2. Configure the test script in package.json

For Node.js projects, open package.json and add a test script under the scripts section. This tells npm how to run your tests when you execute 'npm test' in Shell. Set the test environment to 'node' for Jest. For Python projects, you can skip this step since pytest automatically discovers test files that start with test_ or end with _test.py.

```
{
  "scripts": {
    "test": "jest --verbose --forceExit",
    "test:watch": "jest --watch",
    "start": "node index.js"
  },
  "jest": {
    "testEnvironment": "node",
    "testMatch": ["**/__tests__/**/*.js", "**/*.test.js"]
  }
}
```

**Expected result:** Running 'npm test' in Shell invokes Jest with the configured options. If no test files exist yet, Jest reports 'No tests found' rather than an error.

### 3. Structure your API for testability

Before writing tests, separate your Express app setup from the server startup. Export the app object from your main file so tests can import it without actually starting the server on a port. This pattern is essential for testing since supertest creates its own temporary server. Create an app.js file for the Express configuration and an index.js file that imports the app and calls listen.

```
// app.js - Express configuration (exported for testing)
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());
app.use(express.json());

app.get('/api/health', (req, res) => {
  res.json({ status: 'ok' });
});

app.post('/api/users', (req, res) => {
  const { email, name } = req.body;
  if (!email || !name) {
    return res.status(400).json({ error: 'Email and name are required' });
  }
  res.status(201).json({ id: Date.now(), email, name });
});

app.get('/api/users/:id', (req, res) => {
  const { id } = req.params;
  if (id === '0') {
    return res.status(404).json({ error: 'User not found' });
  }
  res.json({ id, email: 'test@example.com', name: 'Test User' });
});

module.exports = app;

// index.js - Server startup (not imported by tests)
const app = require('./app');
const PORT = process.env.PORT || 3000;
app.listen(PORT, '0.0.0.0', () => {
  console.log(`Server running on port ${PORT}`);
});
```

**Expected result:** Your app.js exports the Express app object. Your index.js imports it and starts the server. Tests will import from app.js directly.

### 4. Write unit tests for your API endpoints

Create a test file that imports your app and uses supertest to make HTTP requests against it. Name the file with the .test.js suffix so Jest automatically discovers it. Write tests that cover successful operations, validation errors, and edge cases. Each test should be independent and not rely on the state created by other tests. The RapidDev engineering team recommends testing at minimum: success cases, validation failures, and not-found scenarios for each endpoint.

```
// __tests__/api.test.js
const request = require('supertest');
const app = require('../app');

describe('Health Check', () => {
  test('GET /api/health returns 200 with status ok', async () => {
    const response = await request(app).get('/api/health');
    expect(response.status).toBe(200);
    expect(response.body.status).toBe('ok');
  });
});

describe('User API', () => {
  test('POST /api/users creates a user with valid data', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ email: 'new@example.com', name: 'New User' });
    expect(response.status).toBe(201);
    expect(response.body).toHaveProperty('id');
    expect(response.body.email).toBe('new@example.com');
  });

  test('POST /api/users returns 400 when email is missing', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ name: 'No Email User' });
    expect(response.status).toBe(400);
    expect(response.body.error).toBe('Email and name are required');
  });

  test('GET /api/users/:id returns user data', async () => {
    const response = await request(app).get('/api/users/123');
    expect(response.status).toBe(200);
    expect(response.body).toHaveProperty('email');
  });

  test('GET /api/users/0 returns 404 for non-existent user', async () => {
    const response = await request(app).get('/api/users/0');
    expect(response.status).toBe(404);
    expect(response.body.error).toBe('User not found');
  });
});
```

**Expected result:** Running 'npm test' shows all tests passing with verbose output. Each test name displays with a checkmark and execution time.

### 5. Run tests from Shell and interpret results

Open Shell and run 'npm test' for Node.js or 'pytest -v' for Python. Jest displays each test with a pass/fail indicator, the test name, and execution time. At the bottom, a summary shows total tests, passed, failed, and duration. If any tests fail, Jest shows the expected value vs the received value with a clear diff. Fix the failing code or test, save, and run again. Use 'npm run test:watch' to automatically re-run tests when files change.

```
# Run all tests
npm test

# Run tests in watch mode (re-runs on file changes)
npm run test:watch

# Run a specific test file
npx jest __tests__/api.test.js

# Run tests matching a pattern
npx jest --testPathPattern="user"
```

**Expected result:** Jest shows a summary of all tests with pass/fail status. Green checkmarks indicate passing tests. Red X marks indicate failures with detailed error output.

### 6. Add tests to the deployment build process

To prevent deploying untested code, add the test command to your deployment build step in the .replit file. This runs your test suite during the build phase, and if any test fails, the deployment is blocked. This is a simple but effective way to ensure only tested code reaches production.

```
[deployment]
run = ["sh", "-c", "node index.js"]
build = ["sh", "-c", "npm install && npm test && npm run build"]
deploymentTarget = "cloudrun"
```

**Expected result:** Deploying the app first runs the test suite. If tests fail, the deployment stops and shows the test failure output in the deployment logs.

## Complete code example

File: `__tests__/api.test.js`

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

describe('Health Check Endpoint', () => {
  test('GET /api/health returns 200 with status ok', async () => {
    const response = await request(app).get('/api/health');
    expect(response.status).toBe(200);
    expect(response.body).toEqual({ status: 'ok' });
  });
});

describe('POST /api/users', () => {
  test('creates a user with valid email and name', async () => {
    const userData = { email: 'test@example.com', name: 'Test User' };
    const response = await request(app)
      .post('/api/users')
      .send(userData)
      .set('Content-Type', 'application/json');

    expect(response.status).toBe(201);
    expect(response.body).toHaveProperty('id');
    expect(response.body.email).toBe(userData.email);
    expect(response.body.name).toBe(userData.name);
  });

  test('returns 400 when email is missing', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ name: 'No Email' });

    expect(response.status).toBe(400);
    expect(response.body).toHaveProperty('error');
    expect(response.body.error).toContain('required');
  });

  test('returns 400 when name is missing', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({ email: 'test@example.com' });

    expect(response.status).toBe(400);
    expect(response.body).toHaveProperty('error');
  });

  test('returns 400 when body is empty', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({});

    expect(response.status).toBe(400);
  });
});

describe('GET /api/users/:id', () => {
  test('returns user data for valid ID', async () => {
    const response = await request(app).get('/api/users/123');

    expect(response.status).toBe(200);
    expect(response.body).toHaveProperty('id');
    expect(response.body).toHaveProperty('email');
    expect(response.body).toHaveProperty('name');
  });

  test('returns 404 for non-existent user', async () => {
    const response = await request(app).get('/api/users/0');

    expect(response.status).toBe(404);
    expect(response.body.error).toBe('User not found');
  });
});
```

## Common mistakes

- **Importing the server file (index.js) instead of the app file (app.js) in tests, causing 'port already in use' errors** — undefined Fix: Export the Express app from a separate app.js file and import only that file in tests. The server startup (app.listen) should be in index.js, which tests never import.
- **Tests pass locally but the deployed app fails because environment variables are missing** — undefined Fix: Mock environment variables in tests using process.env assignments in beforeAll blocks, and verify that deployment secrets match workspace secrets.
- **Jest hangs after tests complete because of open database connections or timers** — undefined Fix: Add --forceExit to your Jest command. Also add afterAll blocks that close database connections and clear timers.
- **Writing tests that depend on data created by previous tests** — undefined Fix: Each test should set up its own data and clean up after itself. Use beforeEach to reset state and afterEach to clean up.

## Best practices

- Separate your Express app setup from server startup by exporting the app object from a dedicated app.js file
- Install testing packages as devDependencies so they are not bundled in production deployments
- Write tests that cover success cases, validation errors, not-found scenarios, and edge cases for each endpoint
- Use descriptive test names that read as sentences explaining expected behavior
- Keep tests independent so each test can run in isolation without depending on other tests
- Use the --forceExit flag with Jest to prevent hanging from unclosed database connections
- Add the test command to your deployment build step to block deployment of untested code
- Mock external dependencies (databases, third-party APIs) so tests run fast and do not require live services

## Frequently asked questions

### What testing framework should I use for Node.js in Replit?

Jest is the most popular choice and works well in Replit. It includes a test runner, assertion library, and mocking tools in one package. Pair it with supertest for HTTP endpoint testing. Mocha and Chai are alternatives but require more configuration.

### Can I test Python Flask APIs in Replit?

Yes. Install pytest with 'pip install pytest' in Shell. Create test files prefixed with test_ (like test_api.py). Flask has a built-in test client: use app.test_client() to make requests without starting the server. Run tests with 'pytest -v' in Shell.

### How do I mock a database in my tests?

Use Jest's jest.mock() to replace your database module with a mock implementation. For example: jest.mock('./db', () => ({ query: jest.fn().mockResolvedValue([{ id: 1 }]) })). This prevents tests from needing a live database connection.

### Why does Jest hang after tests complete in Replit?

Open handles like database connections, HTTP servers, or timers prevent Jest from exiting. Add --forceExit to your test script as a workaround, and close all connections in afterAll blocks for a proper fix.

### Can Replit Agent write tests for me?

Yes. Prompt Agent with: 'Write Jest unit tests for all endpoints in app.js. Include tests for success cases, validation errors, and not-found responses. Create the test file in __tests__/api.test.js.' Agent v4 can generate comprehensive test suites based on your existing API code.

### How do I run only specific tests instead of the full suite?

Use 'npx jest --testPathPattern=filename' to run tests from a specific file. Use 'npx jest -t "test name pattern"' to run tests matching a name pattern. In pytest, use 'pytest test_file.py::test_function_name' for a specific test.

### Should I test the database directly or mock it?

For unit tests, mock the database to keep tests fast and independent. For integration tests, use Replit's built-in PostgreSQL with a separate test database. Clear the test database before each test run to ensure clean state.

### Can RapidDev help set up a comprehensive testing strategy?

Yes. The RapidDev engineering team can help design and implement a testing strategy that includes unit tests, integration tests, and end-to-end tests tailored to your Replit project's architecture and deployment pipeline.

---

Source: https://www.rapidevelopers.com/replit-tutorial/how-to-set-up-automated-unit-tests-for-a-backend-api-within-replit
© RapidDev — https://www.rapidevelopers.com/replit-tutorial/how-to-set-up-automated-unit-tests-for-a-backend-api-within-replit
