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¶
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.
-
Install as dev dependency: SuperTest is a testing tool and should only be in
devDependencies, not production dependencies. -
Don't start the server manually: SuperTest handles server binding automatically. Pass the Express app directly without calling
app.listen(). -
Use request.agent() for session testing: When testing authenticated routes or cookie-based sessions, create an agent to persist cookies across requests.
-
Chain expectations for cleaner tests: Multiple
.expect()calls can be chained for comprehensive assertions in a single test. -
Store responses for complex assertions: Capture the response object when you need to perform multiple assertions on different parts.
-
Test both success and failure cases: Always test error scenarios, validation failures, and edge cases alongside happy paths.
-
Use descriptive test names: Write test descriptions that clearly explain what behavior is being verified.
-
Clean up test data: Use
beforeEach/afterEachhooks to reset database state and ensure test isolation. -
Set appropriate timeouts: For slow endpoints or external API calls, configure timeout values to prevent false failures.
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. |