Skip to main content
RapidDev - Software Development Agency
replit-tutorial

How to run backend unit tests in Replit

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.

What you'll learn

  • Install and configure Jest for Node.js or pytest for Python in Replit
  • Write unit tests for REST API endpoints with request/response validation
  • Mock external dependencies like databases and third-party APIs
  • Configure test scripts in .replit and package.json for easy execution
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced9 min read25-35 minutesAll Replit plans. Works with Node.js (Jest, Mocha) and Python (pytest, unittest). Examples use Jest for Express.js and pytest for Flask.March 2026RapidDev Engineering Team
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.

Prerequisites

  • 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.

typescript
1# For Node.js (Express.js) projects:
2npm install --save-dev jest supertest
3
4# For Python (Flask) projects:
5pip 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.

typescript
1{
2 "scripts": {
3 "test": "jest --verbose --forceExit",
4 "test:watch": "jest --watch",
5 "start": "node index.js"
6 },
7 "jest": {
8 "testEnvironment": "node",
9 "testMatch": ["**/__tests__/**/*.js", "**/*.test.js"]
10 }
11}

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.

typescript
1// app.js - Express configuration (exported for testing)
2const express = require('express');
3const cors = require('cors');
4const app = express();
5
6app.use(cors());
7app.use(express.json());
8
9app.get('/api/health', (req, res) => {
10 res.json({ status: 'ok' });
11});
12
13app.post('/api/users', (req, res) => {
14 const { email, name } = req.body;
15 if (!email || !name) {
16 return res.status(400).json({ error: 'Email and name are required' });
17 }
18 res.status(201).json({ id: Date.now(), email, name });
19});
20
21app.get('/api/users/:id', (req, res) => {
22 const { id } = req.params;
23 if (id === '0') {
24 return res.status(404).json({ error: 'User not found' });
25 }
26 res.json({ id, email: 'test@example.com', name: 'Test User' });
27});
28
29module.exports = app;
30
31// index.js - Server startup (not imported by tests)
32const app = require('./app');
33const PORT = process.env.PORT || 3000;
34app.listen(PORT, '0.0.0.0', () => {
35 console.log(`Server running on port ${PORT}`);
36});

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.

typescript
1// __tests__/api.test.js
2const request = require('supertest');
3const app = require('../app');
4
5describe('Health Check', () => {
6 test('GET /api/health returns 200 with status ok', async () => {
7 const response = await request(app).get('/api/health');
8 expect(response.status).toBe(200);
9 expect(response.body.status).toBe('ok');
10 });
11});
12
13describe('User API', () => {
14 test('POST /api/users creates a user with valid data', async () => {
15 const response = await request(app)
16 .post('/api/users')
17 .send({ email: 'new@example.com', name: 'New User' });
18 expect(response.status).toBe(201);
19 expect(response.body).toHaveProperty('id');
20 expect(response.body.email).toBe('new@example.com');
21 });
22
23 test('POST /api/users returns 400 when email is missing', async () => {
24 const response = await request(app)
25 .post('/api/users')
26 .send({ name: 'No Email User' });
27 expect(response.status).toBe(400);
28 expect(response.body.error).toBe('Email and name are required');
29 });
30
31 test('GET /api/users/:id returns user data', async () => {
32 const response = await request(app).get('/api/users/123');
33 expect(response.status).toBe(200);
34 expect(response.body).toHaveProperty('email');
35 });
36
37 test('GET /api/users/0 returns 404 for non-existent user', async () => {
38 const response = await request(app).get('/api/users/0');
39 expect(response.status).toBe(404);
40 expect(response.body.error).toBe('User not found');
41 });
42});

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.

typescript
1# Run all tests
2npm test
3
4# Run tests in watch mode (re-runs on file changes)
5npm run test:watch
6
7# Run a specific test file
8npx jest __tests__/api.test.js
9
10# Run tests matching a pattern
11npx 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.

typescript
1[deployment]
2run = ["sh", "-c", "node index.js"]
3build = ["sh", "-c", "npm install && npm test && npm run build"]
4deploymentTarget = "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 working example

__tests__/api.test.js
1const request = require('supertest');
2const app = require('../app');
3
4describe('Health Check Endpoint', () => {
5 test('GET /api/health returns 200 with status ok', async () => {
6 const response = await request(app).get('/api/health');
7 expect(response.status).toBe(200);
8 expect(response.body).toEqual({ status: 'ok' });
9 });
10});
11
12describe('POST /api/users', () => {
13 test('creates a user with valid email and name', async () => {
14 const userData = { email: 'test@example.com', name: 'Test User' };
15 const response = await request(app)
16 .post('/api/users')
17 .send(userData)
18 .set('Content-Type', 'application/json');
19
20 expect(response.status).toBe(201);
21 expect(response.body).toHaveProperty('id');
22 expect(response.body.email).toBe(userData.email);
23 expect(response.body.name).toBe(userData.name);
24 });
25
26 test('returns 400 when email is missing', async () => {
27 const response = await request(app)
28 .post('/api/users')
29 .send({ name: 'No Email' });
30
31 expect(response.status).toBe(400);
32 expect(response.body).toHaveProperty('error');
33 expect(response.body.error).toContain('required');
34 });
35
36 test('returns 400 when name is missing', async () => {
37 const response = await request(app)
38 .post('/api/users')
39 .send({ email: 'test@example.com' });
40
41 expect(response.status).toBe(400);
42 expect(response.body).toHaveProperty('error');
43 });
44
45 test('returns 400 when body is empty', async () => {
46 const response = await request(app)
47 .post('/api/users')
48 .send({});
49
50 expect(response.status).toBe(400);
51 });
52});
53
54describe('GET /api/users/:id', () => {
55 test('returns user data for valid ID', async () => {
56 const response = await request(app).get('/api/users/123');
57
58 expect(response.status).toBe(200);
59 expect(response.body).toHaveProperty('id');
60 expect(response.body).toHaveProperty('email');
61 expect(response.body).toHaveProperty('name');
62 });
63
64 test('returns 404 for non-existent user', async () => {
65 const response = await request(app).get('/api/users/0');
66
67 expect(response.status).toBe(404);
68 expect(response.body.error).toBe('User not found');
69 });
70});

Common mistakes when running backend unit tests in Replit

Why it's a problem: Importing the server file (index.js) instead of the app file (app.js) in tests, causing 'port already in use' errors

How to avoid: 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.

Why it's a problem: Tests pass locally but the deployed app fails because environment variables are missing

How to avoid: Mock environment variables in tests using process.env assignments in beforeAll blocks, and verify that deployment secrets match workspace secrets.

Why it's a problem: Jest hangs after tests complete because of open database connections or timers

How to avoid: Add --forceExit to your Jest command. Also add afterAll blocks that close database connections and clear timers.

Why it's a problem: Writing tests that depend on data created by previous tests

How to avoid: 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

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I have an Express.js API in Replit with endpoints for user CRUD operations. Help me set up Jest with supertest to write unit tests. I need tests for POST /api/users (valid data, missing fields), GET /api/users/:id (found, not found), and a health check endpoint. Include the package.json scripts and test file.

Replit Prompt

Set up Jest testing for this Express.js API. Install jest and supertest as devDependencies. Separate the app configuration from server startup. Create a __tests__/api.test.js file with tests for every endpoint. Add a test script to package.json. Make sure each test covers both success and error cases.

Frequently asked questions

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.

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.

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.

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.

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.

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.

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.

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.