Back

Testing RESTful API (Express + MySQL) with JestBlur image

This article will guide you through writing and running Unit Tests for RESTful API that uses Node.js, Express, Prisma ORM and MySQL using testing tools Jest and Supertest

1. Objectives#

  • Learn how to install and configure Jest and Supertest
  • Understand the principles of writing Unit Tests for API
  • Test CRUD Operations functionality
  • Verify HTTP Status Code and Response Data
  • Run automated testing

2. Testing Tools Used#

2.1 Jest#

Jest is a JavaScript Testing Framework developed by Facebook

Advantages:

  • Easy to use, no complex configuration required
  • Built-in Assertion Library
  • Supports Async/Await
  • Clear and readable test result display
  • Includes Code Coverage Report

2.2 Supertest#

Supertest is a Library for testing HTTP Server

Advantages:

  • Works well with Express
  • Supports all types of HTTP Requests
  • Comprehensive Response verification
  • Systematic Test Case writing

3. Project Structure#

Create a new project and install Dependencies

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

3.1 Install Dependencies#

# Install Production Dependencies
npm install express body-parser cors @prisma/client

# Install Development Dependencies  
npm install -D jest supertest prisma
bash

3.2 File Structure#

api-testing-demo/
├── prisma/
│   └── schema.prisma
├── src/
│   └── prisma.js
├── tests/
│   └── api.test.js
├── index.js
├── package.json
└── .env
plaintext

4. Database and Prisma Configuration#

4.1 Create .env file#

DATABASE_URL="mysql://username:password@localhost:3306/test_db"
plaintext

4.2 Configure Prisma Schema#

Create file prisma/schema.prisma:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model User {
  id       Int     @id @default(autoincrement())
  fname    String
  lname    String
  username String  @unique
  email    String  @unique
  avatar   String?
}
prisma

4.3 Create Prisma Client#

Create file src/prisma.js:

const { PrismaClient } = require('@prisma/client');

const prisma = new PrismaClient();

module.exports = prisma;
javascript

4.4 Create Database#

npx prisma migrate dev --name init
npx prisma generate
bash

5. Create API Server#

Create file index.js:

const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const prisma = require('./src/prisma');

const app = express();
const port = 5000;

// Middleware
app.use(cors());
app.use(bodyParser.json());

// Routes
app.get('/', (req, res) => {
  res.send('Hello! RESTful API is ready to use with Prisma')
});

// Get all users
app.get('/users', async (req, res) => {
  const users = await prisma.user.findMany();
  res.json(users);
});

// Get user by ID
app.get('/users/:id', async (req, res) => {
  const id = parseInt(req.params.id);
  const user = await prisma.user.findUnique({ where: { id } });
  if (!user) return res.status(404).json({ message: 'User not found' });
  res.json(user);
});

// Create new user
app.post('/users', async (req, res) => {
  try {
    const newUser = await prisma.user.create({ data: req.body });
    res.status(201).json(newUser);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
});

// Update user
app.put('/users/:id', async (req, res) => {
  try {
    const id = parseInt(req.params.id);
    const updated = await prisma.user.update({
      where: { id },
      data: req.body
    })
    res.json(updated);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
});

// Delete user
app.delete('/users/:id', async (req, res) => {
  try {
    const id = parseInt(req.params.id);
    const deleted = await prisma.user.delete({ where: { id } });
    res.json({ message: `User with ID ${deleted.id} deleted` })
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
});

// Start server only when run directly (not in tests)
if (require.main === module) {
  app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);
  });
}

// Export for testing
module.exports = app;
javascript

Important Code Explanation:

  • require('./src/prisma') - Import Prisma Client
  • prisma.user.findMany() - Retrieve all users
  • prisma.user.findUnique() - Retrieve user by ID
  • prisma.user.create() - Create new user
  • prisma.user.update() - Update user data
  • prisma.user.delete() - Delete user
  • if (require.main === module) - Start server only when running file directly
  • module.exports = app - Export application for testing

6. Write Test Cases#

Create file tests/api.test.js:

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

describe('Prisma API Tests', () => {
  let createdUserId;

  // Test 1: Test Root Endpoint
  it('GET / should return API ready message', async () => {
    const res = await request(app).get('/');
    expect(res.statusCode).toBe(200);
    expect(res.text).toContain('RESTful API is ready');
  });

  // Test 2: Test creating a user
  it('POST /users should create a user', async () => {
    const res = await request(app)
      .post('/users')
      .send({
        fname: 'John',
        lname: 'Smith',
        username: `john_smith_${Date.now()}`, // unique username
        email: `john${Date.now()}@example.com`, // unique email
        avatar: 'https://example.com/avatar.jpg'
      });
    expect(res.statusCode).toBe(201);
    expect(res.body).toHaveProperty('id');
    createdUserId = res.body.id; // Save ID for later tests
  });

  // Test 3: Test retrieving the created user
  it('GET /users/:id should return the created user', async () => {
    const res = await request(app).get(`/users/${createdUserId}`);
    expect(res.statusCode).toBe(200);
    expect(res.body).toHaveProperty('fname', 'John');
    expect(res.body).toHaveProperty('lname', 'Smith');
  });

  // Test 4: Test updating the user
  it('PUT /users/:id should update the user', async () => {
    const res = await request(app)
      .put(`/users/${createdUserId}`)
      .send({ fname: 'Jackson', lname: 'Mars' });
    expect(res.statusCode).toBe(200);
    expect(res.body).toHaveProperty('fname', 'Jackson');
    expect(res.body).toHaveProperty('lname', 'Mars');
  });

  // Test 5: Test deleting the user
  it('DELETE /users/:id should delete the user', async () => {
    const res = await request(app).delete(`/users/${createdUserId}`);
    expect(res.statusCode).toBe(200);
    expect(res.body.message).toContain(`User with ID ${createdUserId} deleted`);
  });

  // Test 6: Test retrieving a deleted user
  it('GET /users/:id should return 404 for deleted user', async () => {
    const res = await request(app).get(`/users/${createdUserId}`);
    expect(res.statusCode).toBe(404);
    expect(res.body).toHaveProperty('message', 'User not found');
  });
});
javascript

7. Test Cases Explanation for Each Section#

7.1 Importing Dependencies#

const request = require('supertest');
const app = require('../index');
javascript
  • supertest - For sending HTTP Requests to Express App
  • ../index - Import Express Application to be tested

7.2 Test Suite Structure#

describe('Prisma API Tests', () => {
  let createdUserId; // Variable to store the ID of the created user
  
  // Test cases will be here
});
javascript
  • describe() - Group related Test Cases together
  • createdUserId - Store ID of created data for use in subsequent tests

7.3 Testing Each Endpoint#

Test 1: Root Endpoint

it('GET / should return API ready message', async () => {
  const res = await request(app).get('/');
  expect(res.statusCode).toBe(200);
  expect(res.text).toContain('RESTful API is ready');
});
javascript
  • it() - Define individual Test Case
  • request(app).get('/') - Send GET Request to Root Path
  • expect(res.statusCode).toBe(200) - Verify Status Code
  • expect(res.text).toContain() - Verify content in Response

Test 2: User Creation

it('POST /users should create a user', async () => {
  const res = await request(app)
    .post('/users')
    .send({
      fname: 'John',
      lname: 'Smith',
      username: `john_smith_${Date.now()}`,
      email: `john${Date.now()}@example.com`,
      avatar: 'https://example.com/avatar.jpg'
    });
  expect(res.statusCode).toBe(201);
  expect(res.body).toHaveProperty('id');
  createdUserId = res.body.id;
});
javascript
  • .send() - Send data in Request Body
  • Date.now() - Create Unique Value to avoid duplicate data
  • toHaveProperty('id') - Verify that Response has that Property
  • Store id for use in subsequent tests

7.4 Commonly Used Jest Matchers#

MatcherMeaningExample
.toBe()Equals (===)expect(status).toBe(200)
.toEqual()Equals (deep equality)expect(obj).toEqual({id: 1})
.toContain()Contains text/valueexpect(text).toContain('success')
.toHaveProperty()Has that Propertyexpect(obj).toHaveProperty('name')
.toBeNull()Is nullexpect(value).toBeNull()
.toBeTruthy()Is truthyexpect(result).toBeTruthy()

8. Configure NPM Scripts#

Add to package.json:

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "start": "node index.js"
  },
  "jest": {
    "testEnvironment": "node",
    "testTimeout": 10000
  }
}
json

Scripts Explanation:

  • npm test - Run all tests
  • npm run test:watch - Run tests and watch for file changes
  • npm run test:coverage - Run tests with Code Coverage Report
  • testTimeout: 10000 - Set timeout to 10 seconds (for Database Operations)

9. Run Tests#

9.1 Initialize Database#

# Create database and tables
npx prisma migrate dev --name init

# Generate Prisma Client
npx prisma generate
bash

9.2 Run Tests#

# Run tests once
npm test

# Run tests in Watch Mode
npm run test:watch

# Run tests with Coverage Report
npm run test:coverage
bash

9.3 Expected Test Results#

 PASS  tests/api.test.js
  Prisma API Tests
    √ GET / should return API ready message (26 ms)
    √ POST /users should create a user (154 ms)
    √ GET /users/:id should return the created user (20 ms)
    √ PUT /users/:id should update the user (13 ms)
    √ DELETE /users/:id should delete the user (8 ms)
    √ GET /users/:id should return 404 for deleted user (6 ms)

Test Suites: 1 passed, 1 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        0.829 s, estimated 1 s
Ran all test suites.
plaintext

10. Additional Error Cases Testing#

Add Test Cases for error scenarios:

describe('Error Handling Tests', () => {
  
  // Test creating a user with incomplete data
  it('POST /users should return 500 for invalid data', async () => {
    const res = await request(app)
      .post('/users')
      .send({
        fname: 'John'
        // Missing lname, username, email
      });
    expect(res.statusCode).toBe(500);
    expect(res.body).toHaveProperty('message');
  });

  // Test retrieving a user that does not exist
  it('GET /users/999999 should return 404', async () => {
    const res = await request(app).get('/users/999999');
    expect(res.statusCode).toBe(404);
    expect(res.body).toHaveProperty('message', 'User not found');
  });

  // Test creating a user with a duplicate email
  it('POST /users should return 500 for duplicate email', async () => {
    const userData = {
      fname: 'Jane',
      lname: 'Doe', 
      username: 'jane_doe_test',
      email: 'duplicate@test.com',
      avatar: 'https://example.com/avatar.jpg'
    };

    // Create user the first time
    await request(app).post('/users').send(userData);
    
    // Attempt to create another user with the same email
    const res = await request(app).post('/users').send({
      ...userData,
      username: 'jane_doe_test2' // Change username but keep the same email
    });
    
    expect(res.statusCode).toBe(500);
    expect(res.body.message).toContain('email');
  });
});
javascript

Expected Test Results

 PASS  tests/api.test.js
  Prisma API Tests
    √ GET / should return API ready message (30 ms)                                                                                   
    √ POST /users should create a user (28 ms)                                                                                        
    √ GET /users/:id should return the created user (7 ms)                                                                            
    √ PUT /users/:id should update the user (8 ms)                                                                                    
    √ DELETE /users/:id should delete the user (8 ms)                                                                                 
    √ GET /users/:id should return 404 for deleted user (5 ms)                                                                        
  Error Handling Tests                                                                                                                
    √ POST /users should return 500 for invalid data (133 ms)                                                                         
    √ GET /users/999999 should return 404 (6 ms)                                                                                      
    √ POST /users should return 500 for duplicate email (17 ms)                                                                       
                                                                                                                                      
Test Suites: 1 passed, 1 total                                                                                                        
Tests:       9 passed, 9 total                                                                                                        
Snapshots:   0 total
Time:        0.686 s, estimated 1 s
Ran all test suites.
plaintext

11. Code Coverage#

Check Code Coverage to see how much of the code is tested:

npm run test:coverage
bash

Displayed Results:

 PASS  tests/api.test.js                                                                                                              
  Prisma API Tests
    √ GET / should return API ready message (25 ms)                                                                                   
    √ POST /users should create a user (28 ms)                                                                                        
    √ GET /users/:id should return the created user (6 ms)                                                                            
    √ PUT /users/:id should update the user (8 ms)                                                                                    
    √ DELETE /users/:id should delete the user (8 ms)                                                                                 
    √ GET /users/:id should return 404 for deleted user (6 ms)                                                                        
  Error Handling Tests                                                                                                                
    √ POST /users should return 500 for invalid data (141 ms)                                                                         
    √ GET /users/999999 should return 404 (5 ms)                                                                                      
    √ POST /users should return 500 for duplicate email (16 ms)                                                                       

----------------|---------|----------|---------|---------|-------------------
File            | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------|---------|----------|---------|---------|-------------------
All files       |   86.04 |       75 |   71.42 |   85.71 | 
 api-prisma     |      85 |       75 |   71.42 |   84.61 | 
  index.js      |      85 |       75 |   71.42 |   84.61 | 17-18,46,56,61-62
 api-prisma/src |     100 |      100 |     100 |     100 | 
  prisma.js     |     100 |      100 |     100 |     100 | 
----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       9 passed, 9 total
Snapshots:   0 total
Time:        0.749 s, estimated 1 s
Ran all test suites.
plaintext

Explanation:

  • % Stmts - Percentage of Statements tested
  • % Branch - Percentage of Conditional Branches tested
  • % Funcs - Percentage of Functions tested
  • % Lines - Percentage of code lines tested

12. Best Practices for API Testing#

✅ What to do:

  • Use unique data in each test (e.g., timestamp)
  • Test both Success and Error Cases
  • Clean up data after testing
  • Write understandable Test Cases
  • Use describe() to group Test Cases
  • Verify both Status Code and Response Data

❌ What to avoid:

  • Use hard-coded data that might duplicate
  • Write Test Cases that depend too much on each other
  • Not testing Error Cases
  • Use unclear Test Case names
  • Not verifying complete Response

API testing is a crucial part of modern software development. These tools help ensure development quality and reliability

Testing RESTful API (Express + MySQL) with Jest
Author กานต์ ยงศิริวิทย์ / Karn Yongsiriwit
Published at July 20, 2025

Loading comments...

Comments 0