Complete Express API Testing Guide with Step-by-Step Procedures

Initial Setup

Before we begin testing our API endpoints, we need to set up our testing environment properly. This setup will ensure we can test efficiently and systematically.

Setting Up Postman

Let's create a complete testing environment in Postman that we'll use throughout our testing:

  1. Create a New Collection
    • Open Postman and click "Create Collection"
    • Name it "Express Authentication API"
    • Click "Create"
  2. Set Up Environment Variables
    • Click "Environments" in the sidebar
    • Click "Create Environment"
    • Name it "Local Development"
    • Add these variables:
      baseUrl: http://localhost:8000
      xsrfToken: [leave empty initially]
      authToken: [leave empty initially]
    • Save the environment
  3. Create Collection Pre-request Script
    • Select your collection
    • Go to the "Pre-request Scripts" tab
    • Add this script to automatically fetch CSRF token:
      pm.sendRequest({
          url: pm.environment.get("baseUrl") + "/api/csrf/restore",
          method: 'GET'
      }, function (err, res) {
          if (!err) {
              pm.environment.set("xsrfToken", res.cookies.get("XSRF-TOKEN"));
          }
      });

Setting Up for cURL Testing

For cURL testing, we'll need to store our CSRF token for repeated use. Let's create a script to help with this:

#!/bin/bash
# Save as get-csrf.sh

# Get CSRF token and save it
TOKEN=$(curl -s -X GET http://localhost:8000/api/csrf/restore \
  --cookie-jar cookies.txt \
  | grep -o '"XSRF-Token":"[^"]*' \
  | cut -d'"' -f4)

echo "CSRF Token: $TOKEN"
echo "Token saved to cookies.txt"

Make the script executable:

chmod +x get-csrf.sh

Testing User Registration (POST /api/users)

Postman Step-by-Step Procedure

  1. Create New Request
    • In your collection, click "Add Request"
    • Name it "User Registration"
    • Set method to POST
    • Set URL to: {{baseUrl}}/api/users
  2. Configure Headers
    • Add Content-Type: application/json
    • Add XSRF-TOKEN: {{xsrfToken}}
  3. Configure Body
    • Select "raw" and "JSON"
    • Add registration data:
      {
          "firstName": "John",
          "lastName": "Smith",
          "email": "john.smith@gmail.com",
          "username": "JohnSmith",
          "password": "secret password"
      }
  4. Add Tests
    • Click the "Tests" tab
    • Add test script:
      pm.test("Status code is 201", function () {
          pm.response.to.have.status(201);
      });
      
      pm.test("User is created successfully", function () {
          const responseJson = pm.response.json();
          pm.expect(responseJson.user).to.have.property('id');
          pm.expect(responseJson.user.username).to.eql('JohnSmith');
      });
      
      if (pm.response.code === 201) {
          pm.environment.set("authToken", pm.response.headers.get("Set-Cookie"));
      }

cURL Step-by-Step Procedure

  1. Get CSRF Token
    ./get-csrf.sh
  2. Save Registration Data
    cat > register.json << EOF
    {
        "firstName": "John",
        "lastName": "Smith",
        "email": "john.smith@gmail.com",
        "username": "JohnSmith",
        "password": "secret password"
    }
    EOF
  3. Send Registration Request
    curl -X POST http://localhost:8000/api/users \
      -H "Content-Type: application/json" \
      -H "XSRF-TOKEN: $TOKEN" \
      -b cookies.txt \
      -d @register.json

Testing Validation Rules

Test each validation rule using both Postman and cURL:

Invalid Email Format

Postman:

  1. Modify the request body to use invalid email:
    {
        "firstName": "John",
        "lastName": "Smith",
        "email": "invalid-email",
        "username": "JohnSmith",
        "password": "secret password"
    }
  2. Send request and verify 400 status code

cURL:

curl -X POST http://localhost:8000/api/users \
  -H "Content-Type: application/json" \
  -H "XSRF-TOKEN: $TOKEN" \
  -b cookies.txt \
  -d '{"firstName":"John","lastName":"Smith","email":"invalid-email","username":"JohnSmith","password":"secret password"}'

Testing User Login (POST /api/session)

Postman Step-by-Step Procedure

  1. Create New Request
    • Name it "User Login"
    • Set method to POST
    • URL: {{baseUrl}}/api/session
  2. Configure Headers
    • Content-Type: application/json
    • XSRF-TOKEN: {{xsrfToken}}
  3. Configure Body
    {
        "credential": "john.smith@gmail.com",
        "password": "secret password"
    }
  4. Add Tests
    pm.test("Status code is 200", function () {
        pm.response.to.have.status(200);
    });
    
    pm.test("Login successful", function () {
        const responseJson = pm.response.json();
        pm.expect(responseJson.user).to.have.property('id');
        pm.expect(responseJson.user.email).to.eql('john.smith@gmail.com');
    });
    
    if (pm.response.code === 200) {
        pm.environment.set("authToken", pm.response.headers.get("Set-Cookie"));
    }

cURL Step-by-Step Procedure

  1. Prepare Login Data
    cat > login.json << EOF
    {
        "credential": "john.smith@gmail.com",
        "password": "secret password"
    }
    EOF
  2. Send Login Request
    curl -X POST http://localhost:8000/api/session \
      -H "Content-Type: application/json" \
      -H "XSRF-TOKEN: $TOKEN" \
      -b cookies.txt \
      -c cookies.txt \
      -d @login.json

Testing Session Management (GET /api/session)

Postman Step-by-Step Procedure

  1. Create New Request
    • Name it "Get Current Session"
    • Set method to GET
    • URL: {{baseUrl}}/api/session
  2. Configure Headers
    • Cookie: {{authToken}}
  3. Add Tests
    pm.test("Status code is 200", function () {
        pm.response.to.have.status(200);
    });
    
    pm.test("Session user data is correct", function () {
        const responseJson = pm.response.json();
        pm.expect(responseJson.user).to.not.be.null;
        pm.expect(responseJson.user).to.have.property('email');
    });

cURL Step-by-Step Procedure

curl http://localhost:8000/api/session \
  -b cookies.txt

Testing Logout (DELETE /api/session)

Postman Step-by-Step Procedure

  1. Create New Request
    • Name it "Logout"
    • Set method to DELETE
    • URL: {{baseUrl}}/api/session
  2. Configure Headers
    • XSRF-TOKEN: {{xsrfToken}}
    • Cookie: {{authToken}}
  3. Add Tests
    pm.test("Status code is 200", function () {
        pm.response.to.have.status(200);
    });
    
    pm.test("Logout successful", function () {
        const responseJson = pm.response.json();
        pm.expect(responseJson.message).to.eql('success');
    });
    
    pm.environment.set("authToken", null);

cURL Step-by-Step Procedure

curl -X DELETE http://localhost:8000/api/session \
  -H "XSRF-TOKEN: $TOKEN" \
  -b cookies.txt

Creating a Complete Test Flow

Now that we understand how to test each endpoint individually, let's create a complete test flow that simulates a user's journey through our application.

Postman Collection Runner Setup

  1. Organize Requests
    • Put requests in this order:
      1. User Registration
      2. User Login
      3. Get Current Session
      4. Logout
  2. Configure Runner
    • Click "Runner" in Postman
    • Select your collection
    • Set environment to "Local Development"
    • Enable "Keep variable values"

cURL Complete Flow Script

#!/bin/bash
# Save as test-flow.sh

echo "Starting API test flow..."

# Get CSRF token
./get-csrf.sh

# Register new user
echo "Testing registration..."
curl -X POST http://localhost:8000/api/users \
  -H "Content-Type: application/json" \
  -H "XSRF-TOKEN: $TOKEN" \
  -b cookies.txt \
  -c cookies.txt \
  -d @register.json

# Login
echo "Testing login..."
curl -X POST http://localhost:8000/api/session \
  -H "Content-Type: application/json" \
  -H "XSRF-TOKEN: $TOKEN" \
  -b cookies.txt \
  -c cookies.txt \
  -d @login.json

# Check current session
echo "Testing current session..."
curl http://localhost:8000/api/session \
  -b cookies.txt

# Logout
echo "Testing logout..."
curl -X DELETE http://localhost:8000/api/session \
  -H "XSRF-TOKEN: $TOKEN" \
  -b cookies.txt

echo "API test flow completed."

Make the script executable:

chmod +x test-flow.sh

Error Handling Testing

Proper error handling is crucial for a robust API. Let's systematically test error scenarios to ensure our API handles them gracefully.

Common Error Scenarios to Test

1. Invalid Credentials

Postman:

  1. Use the "User Login" request
  2. Modify the password to an incorrect value:
  3. {
        "credential": "john.smith@gmail.com",
        "password": "wrong_password"
    }
  4. Add Test:
  5. pm.test("Invalid credentials return 401", function () {
        pm.response.to.have.status(401);
        const responseJson = pm.response.json();
        pm.expect(responseJson).to.have.property('message');
    });

cURL:

curl -X POST http://localhost:8000/api/session \
  -H "Content-Type: application/json" \
  -H "XSRF-TOKEN: $TOKEN" \
  -b cookies.txt \
  -d '{"credential":"john.smith@gmail.com","password":"wrong_password"}'

2. Resource Not Found

Postman:

  1. Create a new request
  2. Set method to GET
  3. URL: {{baseUrl}}/api/nonexistent-endpoint
  4. Add Test:
  5. pm.test("Not found returns 404", function () {
        pm.response.to.have.status(404);
    });

cURL:

curl -i http://localhost:8000/api/nonexistent-endpoint

3. Unauthorized Access

Postman:

  1. Use the "Get Current Session" request
  2. Clear the Cookie header value
  3. Add Test:
  4. pm.test("Unauthorized returns 401", function () {
        pm.response.to.have.status(401);
    });

cURL:

# First make sure we're logged out
curl -X DELETE http://localhost:8000/api/session -H "XSRF-TOKEN: $TOKEN" -b cookies.txt

# Then try to access a protected resource
curl -i http://localhost:8000/api/protected-route

4. Invalid Request Format

Postman:

  1. Use the "User Registration" request
  2. Replace JSON body with an invalid format:
  3. {
        "firstName": "John",
        "lastName": "Smith",
        "email": "john.smith@gmail.com",
        invalid format here
    }
  4. Add Test:
  5. pm.test("Invalid format returns 400", function () {
        pm.response.to.have.status(400);
    });

cURL:

curl -X POST http://localhost:8000/api/users \
  -H "Content-Type: application/json" \
  -H "XSRF-TOKEN: $TOKEN" \
  -b cookies.txt \
  -d '{invalid json format}'

Security Testing

Security is paramount for any API. Let's test common security concerns to ensure our API is protected against common vulnerabilities.

CSRF Protection Testing

Test that requests without CSRF tokens are rejected:

Postman:

  1. Use the "User Login" request
  2. Remove the XSRF-TOKEN header
  3. Add Test:
  4. pm.test("Missing CSRF token returns 403", function () {
        pm.response.to.have.status(403);
    });

cURL:

curl -X POST http://localhost:8000/api/session \
  -H "Content-Type: application/json" \
  -b cookies.txt \
  -d @login.json

Authentication Testing

Test that only authenticated users can access protected routes:

Postman:

  1. Create a new request to a protected route
  2. Try with and without the Auth token
  3. Add Test:
  4. pm.test("Protected route requires authentication", function () {
        // When unauthenticated
        pm.response.to.have.status(401);
    });

cURL:

# Clear cookies to ensure we're not authenticated
rm cookies.txt

# Attempt to access protected route
curl -i http://localhost:8000/api/protected-route

Performance Testing

Understanding how your API performs under load is crucial for production readiness.

Basic Response Time Testing

In Postman, you can measure response times for each request:

pm.test("Response time is acceptable", function () {
    pm.expect(pm.response.responseTime).to.be.below(500);
});

Load Testing with Apache Bench

Test how your API handles multiple concurrent requests:

# Install Apache Bench if not already installed
# For Ubuntu/Debian:
# sudo apt-get install apache2-utils

# For basic load test (10 concurrent users, 100 total requests):
ab -n 100 -c 10 http://localhost:8000/api/session

# For testing with authentication:
ab -n 100 -c 10 -C "yourCookieName=yourCookieValue" http://localhost:8000/api/protected-route

Continuous Monitoring

Set up continuous monitoring to track API performance over time:

#!/bin/bash
# Save as monitor.sh

while true; do
    echo "$(date): Testing API performance..."
    
    # Test login endpoint
    start=$(date +%s%N)
    curl -s -o /dev/null -w "%{http_code}" -X POST http://localhost:8000/api/session \
      -H "Content-Type: application/json" \
      -H "XSRF-TOKEN: $TOKEN" \
      -b cookies.txt \
      -d @login.json
    end=$(date +%s%N)
    
    # Calculate duration in milliseconds
    duration=$(( (end - start) / 1000000 ))
    echo "Login response time: ${duration}ms"
    
    sleep 60  # Wait for 1 minute before next test
done

Automating API Tests

To ensure consistent testing, let's set up automated testing that can be integrated into your CI/CD pipeline.

Setting Up Jest for API Testing

Create a testing framework using Jest:

  1. Install dependencies:
    npm install --save-dev jest supertest
  2. Create a test file (e.g., api.test.js):
    const request = require('supertest');
    const app = require('../app'); // Your Express app
    
    describe('API Authentication Tests', () => {
      let authCookie;
      let csrfToken;
    
      // Before tests, get CSRF token
      beforeAll(async () => {
        const res = await request(app).get('/api/csrf/restore');
        csrfToken = res.body.csrfToken;
      });
    
      // Test user registration
      test('User registration', async () => {
        const res = await request(app)
          .post('/api/users')
          .set('XSRF-TOKEN', csrfToken)
          .send({
            firstName: 'Test',
            lastName: 'User',
            email: 'test.user@example.com',
            username: 'TestUser',
            password: 'testpassword'
          });
          
        expect(res.statusCode).toBe(201);
        expect(res.body.user).toHaveProperty('id');
        expect(res.body.user.username).toBe('TestUser');
        
        // Store cookie for future requests
        authCookie = res.headers['set-cookie'];
      });
    
      // Test user login
      test('User login', async () => {
        const res = await request(app)
          .post('/api/session')
          .set('XSRF-TOKEN', csrfToken)
          .send({
            credential: 'test.user@example.com',
            password: 'testpassword'
          });
          
        expect(res.statusCode).toBe(200);
        expect(res.body.user).toHaveProperty('email');
        expect(res.body.user.email).toBe('test.user@example.com');
        
        // Update auth cookie
        authCookie = res.headers['set-cookie'];
      });
    
      // Test get session
      test('Get current session', async () => {
        const res = await request(app)
          .get('/api/session')
          .set('Cookie', authCookie);
          
        expect(res.statusCode).toBe(200);
        expect(res.body.user).not.toBeNull();
      });
    
      // Test logout
      test('User logout', async () => {
        const res = await request(app)
          .delete('/api/session')
          .set('XSRF-TOKEN', csrfToken)
          .set('Cookie', authCookie);
          
        expect(res.statusCode).toBe(200);
        expect(res.body.message).toBe('success');
      });
    });
  3. Add to package.json:
    "scripts": {
      "test": "jest"
    }
  4. Run tests:
    npm test

Integrating with CI/CD

Example GitHub Actions workflow file (.github/workflows/api-tests.yml):

name: API Tests

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:13
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: express_api_test
        ports:
          - 5432:5432
        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '16'
    - name: Install dependencies
      run: npm ci
    - name: Setup Database
      run: npm run db:setup:test
    - name: Run API tests
      run: npm test

Best Practices and Troubleshooting

API Testing Best Practices

Common Issues and Solutions

CSRF Token Issues

Symptoms: 403 Forbidden errors when sending POST/PUT/DELETE requests.

Solutions:

  • Ensure your CSRF token is being correctly set in the request headers.
  • Check that the CSRF token is up-to-date (they typically expire).
  • Verify the token name matches what the server expects (case-sensitive).

Cookie/Authentication Problems

Symptoms: 401 Unauthorized errors when accessing protected routes.

Solutions:

  • Ensure cookies are being properly stored and sent with requests.
  • Check for cookie expiration or session timeout issues.
  • Verify the cookie is being set with the correct domain and path.

Rate Limiting

Symptoms: 429 Too Many Requests errors during load testing.

Solutions:

  • Add delays between requests in your test scripts.
  • Distribute load tests over a longer period.
  • Use different IP addresses or credentials for testing if possible.

Conclusion

A comprehensive testing strategy is essential for building reliable APIs. By following the procedures in this guide, you'll be able to thoroughly test your Express API's authentication flow, error handling, security, and performance characteristics.

Remember that testing is an ongoing process. As you add new features or modify existing ones, update your test suite accordingly to ensure continued reliability and performance.