Skip to content

SuperTest Cheatsheet

Installation

Platform Command
npm (All Platforms) npm install supertest --save-dev
Yarn (All Platforms) yarn add supertest --dev
With Jest npm install jest supertest --save-dev
With Mocha npm install mocha supertest --save-dev
With TypeScript npm install supertest @types/supertest --save-dev
Specific Version npm install supertest@6.3.3 --save-dev

Basic HTTP Methods

Command Description
request(app).get('/api/users') Send GET request to endpoint
request(app).post('/api/users') Send POST request to endpoint
request(app).put('/api/users/123') Send PUT request to update resource
request(app).patch('/api/users/123') Send PATCH request for partial update
request(app).delete('/api/users/123') Send DELETE request to remove resource
request(app).head('/api/users') Send HEAD request (headers only)
request(app).options('/api/users') Send OPTIONS request for CORS preflight
.send({ name: 'John' }) Send JSON data in request body
.query({ page: 1, limit: 10 }) Add query parameters to URL
.set('Authorization', 'Bearer token') Set request header
.expect(200) Assert HTTP status code
.expect('Content-Type', /json/) Assert response header value
.end((err, res) => {}) Execute request with callback
await request(app).get('/api/users') Execute request with Promise/async-await

Assertions & Expectations

Command Description
.expect(200) Expect exact status code
.expect(200, { id: 1 }) Expect status and exact body match
.expect('Content-Type', 'application/json') Expect exact header value
.expect('Content-Type', /json/) Expect header matching regex
.expect((res) => { if (!res.body) throw new Error() }) Custom assertion function
.expect(200, /success/) Expect status and body matching regex
.expect('Location', '/new-url') Expect redirect location header
.expect({ error: 'Not found' }) Expect exact JSON body match

Request Configuration

Command Description
.set('Authorization', 'Bearer token') Set single request header
.set({ 'X-API-Key': 'key', 'Accept': 'json' }) Set multiple headers
.auth('username', 'password') Set HTTP Basic Authentication
.field('name', 'value') Add form field (multipart)
.attach('file', 'path/to/file.pdf') Attach file for upload
.attach('file', buffer, 'filename.pdf') Attach file from buffer
.timeout(5000) Set request timeout in milliseconds
.redirects(5) Follow up to 5 redirects
.type('json') Set Content-Type header
.accept('json') Set Accept header
.send('raw string data') Send raw string body
.send(Buffer.from('data')) Send binary data

Advanced Usage

Command Description
request.agent(app) Create agent for persistent cookies
agent.get('/api/profile') Use agent for authenticated requests
request('https://api.example.com') Test external API endpoints
.parse((res, callback) => {}) Custom response parser
.buffer(true) Buffer response body
.responseType('blob') Set expected response type
.retry(3) Retry failed requests 3 times
.ca(cert) Set custom CA certificate
.cert(cert) Set client certificate
.key(key) Set client private key
.pfx(pfxData) Set PFX/PKCS12 certificate
.disableTLSCerts() Disable TLS certificate validation

Testing Patterns

Pattern Description
const res = await request(app).get('/api/users') Store response for multiple assertions
expect(res.body).toHaveLength(10) Assert on response body with test framework
expect(res.headers['x-custom']).toBe('value') Assert on response headers
expect(res.status).toBe(200) Alternative status assertion
Promise.all([request(app).get('/a'), request(app).get('/b')]) Parallel request testing
for (const user of users) { await request(app).post('/api/users').send(user) } Sequential test requests

Configuration

Basic Test Setup

// test/setup.js
const request = require('supertest');
const app = require('../app');

module.exports = { request, app };

Jest Configuration

// jest.config.js
module.exports = {
  testEnvironment: 'node',
  testMatch: ['**/__tests__/**/*.test.js'],
  collectCoverageFrom: ['src/**/*.js'],
  coveragePathIgnorePatterns: ['/node_modules/']
};

Mocha Configuration

// test/mocha.opts
--require test/setup.js
--recursive
--timeout 5000
--exit

Package.json Scripts

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:api": "jest --testPathPattern=api"
  }
}

Environment Variables

// config/test.js
module.exports = {
  port: process.env.TEST_PORT || 3001,
  database: process.env.TEST_DB || 'test_db',
  apiTimeout: process.env.API_TIMEOUT || 5000
};

Common Use Cases

Use Case 1: Testing REST API CRUD Operations

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

describe('User API', () => {
  let userId;

  // Create
  test('POST /api/users - create user', async () => {
    const res = await request(app)
      .post('/api/users')
      .send({ name: 'John Doe', email: 'john@example.com' })
      .expect(201)
      .expect('Content-Type', /json/);

    userId = res.body.id;
    expect(res.body.name).toBe('John Doe');
  });

  // Read
  test('GET /api/users/:id - get user', async () => {
    await request(app)
      .get(`/api/users/${userId}`)
      .expect(200)
      .expect((res) => {
        expect(res.body.name).toBe('John Doe');
      });
  });

  // Update
  test('PUT /api/users/:id - update user', async () => {
    await request(app)
      .put(`/api/users/${userId}`)
      .send({ name: 'Jane Doe' })
      .expect(200);
  });

  // Delete
  test('DELETE /api/users/:id - delete user', async () => {
    await request(app)
      .delete(`/api/users/${userId}`)
      .expect(204);
  });
});

Use Case 2: Testing Authentication Flow

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

describe('Authentication', () => {
  let authToken;

  test('POST /auth/register - register new user', async () => {
    await request(app)
      .post('/auth/register')
      .send({
        email: 'test@example.com',
        password: 'SecurePass123!',
        name: 'Test User'
      })
      .expect(201);
  });

  test('POST /auth/login - login user', async () => {
    const res = await request(app)
      .post('/auth/login')
      .send({
        email: 'test@example.com',
        password: 'SecurePass123!'
      })
      .expect(200)
      .expect('Content-Type', /json/);

    authToken = res.body.token;
    expect(authToken).toBeDefined();
  });

  test('GET /api/profile - access protected route', async () => {
    await request(app)
      .get('/api/profile')
      .set('Authorization', `Bearer ${authToken}`)
      .expect(200)
      .expect((res) => {
        expect(res.body.email).toBe('test@example.com');
      });
  });

  test('GET /api/profile - reject without token', async () => {
    await request(app)
      .get('/api/profile')
      .expect(401);
  });
});

Use Case 3: Testing File Upload

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

describe('File Upload', () => {
  test('POST /api/upload - upload single file', async () => {
    const filePath = path.join(__dirname, 'fixtures', 'test.pdf');

    const res = await request(app)
      .post('/api/upload')
      .attach('document', filePath)
      .field('title', 'Test Document')
      .field('description', 'A test file upload')
      .expect(200)
      .expect('Content-Type', /json/);

    expect(res.body.filename).toMatch(/\.pdf$/);
    expect(res.body.size).toBeGreaterThan(0);
  });

  test('POST /api/upload - upload multiple files', async () => {
    await request(app)
      .post('/api/upload/multiple')
      .attach('files', 'test/fixtures/file1.pdf')
      .attach('files', 'test/fixtures/file2.pdf')
      .expect(200)
      .expect((res) => {
        expect(res.body.files).toHaveLength(2);
      });
  });
});

Use Case 4: Testing with Persistent Sessions

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

describe('Session Management', () => {
  const agent = request.agent(app);

  test('Login and maintain session', async () => {
    // Login
    await agent
      .post('/auth/login')
      .send({ username: 'user', password: 'pass' })
      .expect(200);

    // Session persists - can access protected route
    await agent
      .get('/api/dashboard')
      .expect(200);

    // Session still valid for subsequent requests
    await agent
      .get('/api/profile')
      .expect(200);

    // Logout
    await agent
      .post('/auth/logout')
      .expect(200);

    // Session expired - access denied
    await agent
      .get('/api/dashboard')
      .expect(401);
  });
});

Use Case 5: Testing Error Handling

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

describe('Error Handling', () => {
  test('404 - Resource not found', async () => {
    await request(app)
      .get('/api/users/99999')
      .expect(404)
      .expect((res) => {
        expect(res.body.error).toBe('User not found');
        expect(res.body.code).toBe('USER_NOT_FOUND');
      });
  });

  test('400 - Validation error', async () => {
    await request(app)
      .post('/api/users')
      .send({ name: '' }) // Invalid data
      .expect(400)
      .expect((res) => {
        expect(res.body.errors).toBeDefined();
        expect(res.body.errors.name).toContain('required');
      });
  });

  test('429 - Rate limit exceeded', async () => {
    // Make multiple requests to trigger rate limit
    const requests = Array(101).fill().map(() =>
      request(app).get('/api/users')
    );

    const responses = await Promise.all(requests);
    const rateLimited = responses.find(r => r.status === 429);

    expect(rateLimited).toBeDefined();
    expect(rateLimited.body.error).toMatch(/rate limit/i);
  });

  test('500 - Internal server error', async () => {
    await request(app)
      .get('/api/trigger-error')
      .expect(500)
      .expect((res) => {
        expect(res.body.error).toBe('Internal server error');
      });
  });
});

Best Practices

  • Use async/await syntax: Cleaner and more readable than callbacks. Modern JavaScript supports this pattern natively.

    const res = await request(app).get('/api/users').expect(200);
    

  • Install as dev dependency: SuperTest is a testing tool and should only be in devDependencies, not production dependencies.

    npm install supertest --save-dev
    

  • Don't start the server manually: SuperTest handles server binding automatically. Pass the Express app directly without calling app.listen().

    // Good: Export app without listening
    module.exports = app;
    
    // Bad: Don't do this in test files
    app.listen(3000);
    

  • Use request.agent() for session testing: When testing authenticated routes or cookie-based sessions, create an agent to persist cookies across requests.

    const agent = request.agent(app);
    await agent.post('/login').send(credentials);
    await agent.get('/protected'); // Cookies persist
    

  • Chain expectations for cleaner tests: Multiple .expect() calls can be chained for comprehensive assertions in a single test.

    await request(app)
      .get('/api/users')
      .expect(200)
      .expect('Content-Type', /json/)
      .expect((res) => expect(res.body).toHaveLength(10));
    

  • Store responses for complex assertions: Capture the response object when you need to perform multiple assertions on different parts.

    const res = await request(app).get('/api/users').expect(200);
    expect(res.body).toHaveLength(5);
    expect(res.headers['x-total-count']).toBe('100');
    

  • Test both success and failure cases: Always test error scenarios, validation failures, and edge cases alongside happy paths.

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

  • Use descriptive test names: Write test descriptions that clearly explain what behavior is being verified.

    test('POST /api/users returns 400 when email is missing', async () => {
      // Test implementation
    });
    

  • Clean up test data: Use beforeEach/afterEach hooks to reset database state and ensure test isolation.

    afterEach(async () => {
      await User.deleteMany({});
    });
    

  • Set appropriate timeouts: For slow endpoints or external API calls, configure timeout values to prevent false failures.

    await request(app).get('/api/slow').timeout(10000).expect(200);
    

Troubleshooting

Issue Solution
EADDRINUSE: address already in use Don't call app.listen() in your app file when testing. Export the app without starting the server, or use different ports for testing.
Tests hang and never complete Ensure database connections are closed after tests. Use afterAll() to close connections: afterAll(() => mongoose.connection.close())
Cannot read property 'body' of undefined Missing await keyword or .end() callback. Always use await with async/await or provide .end() callback.
Headers already sent error You're sending multiple responses in your route handler. Ensure only one res.send(), res.json(), or similar call per request.
Timeout errors on slow endpoints Increase timeout with .timeout(ms) or adjust global timeout in test framework config: jest.setTimeout(10000)
Authentication tests fail randomly Use request.agent() to persist cookies/sessions across requests instead of separate request() calls.
File upload tests fail Verify file path is correct using path.join(__dirname, 'fixtures', 'file.pdf'). Ensure multipart middleware is configured in app.
CORS errors in tests CORS is typically a browser concern. SuperTest bypasses CORS. If testing CORS headers, use .options() request with Origin header.
SSL/TLS certificate errors Use .disableTLSCerts() for testing environments or provide valid certificates with .ca(), .cert(), .key() methods.
Response body is empty Buffer Response might be binary. Use .buffer(true) or check Content-Type. For JSON, ensure server sends correct Content-Type header.
Tests pass individually but fail together Tests aren't isolated. Clear database/state between tests using beforeEach/afterEach hooks. Check for shared mutable state.
Cannot test external APIs SuperTest works with external URLs: request('https://api.example.com').get('/endpoint'). Ensure network access and API availability.