testing-best-practices
Testing Best Practices
This skill provides comprehensive expert knowledge of testing Node.js/Express applications with emphasis on Jest and Supertest, test organization, mocking strategies, and achieving comprehensive test coverage.
Testing Framework Setup
Jest Installation and Configuration
Install dependencies:
npm install --save-dev jest supertest @types/jest
package.json configuration:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:verbose": "jest --verbose"
},
"jest": {
"testEnvironment": "node",
"coveragePathIgnorePatterns": [
"/node_modules/"
],
"testMatch": [
"**/__tests__/**/*.js",
"**/?(*.)+(spec|test).js"
]
}
}
jest.config.js (advanced):
module.exports = {
// Use Node.js test environment
testEnvironment: 'node',
// Test file patterns
testMatch: [
'**/__tests__/**/*.js',
'**/*.test.js',
'**/*.spec.js'
],
// Coverage settings
collectCoverageFrom: [
'src/**/*.js',
'routes/**/*.js',
'!src/index.js', // Exclude entry point
'!**/node_modules/**'
],
// Coverage thresholds
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
// Setup files
setupFilesAfterEnv: ['<rootDir>/test/setup.js'],
// Clear mocks between tests
clearMocks: true,
// Verbose output
verbose: true,
// Timeout for tests
testTimeout: 10000
};
Test Directory Structure
Option 1: Separate test directory:
project/
├── src/
│ ├── server.js
│ ├── routes/
│ │ └── api.js
│ └── utils/
│ └── validators.js
├── test/
│ ├── setup.js
│ ├── server.test.js
│ ├── routes/
│ │ └── api.test.js
│ └── utils/
│ └── validators.test.js
└── package.json
Option 2: Co-located tests:
project/
├── src/
│ ├── server.js
│ ├── server.test.js
│ ├── routes/
│ │ ├── api.js
│ │ └── api.test.js
│ └── utils/
│ ├── validators.js
│ └── validators.test.js
└── package.json
Option 3: tests directories:
project/
├── src/
│ ├── __tests__/
│ │ └── server.test.js
│ ├── server.js
│ ├── routes/
│ │ ├── __tests__/
│ │ │ └── api.test.js
│ │ └── api.js
└── package.json
Testing Express Applications with Supertest
Basic API Testing
const request = require('supertest');
const app = require('../server');
describe('GET /', () => {
it('should return 200 status', async () => {
const response = await request(app).get('/');
expect(response.status).toBe(200);
});
it('should return JSON content type', async () => {
const response = await request(app).get('/api/users');
expect(response.headers['content-type']).toMatch(/json/);
});
it('should return users array', async () => {
const response = await request(app).get('/api/users');
expect(response.body).toHaveProperty('users');
expect(Array.isArray(response.body.users)).toBe(true);
});
});
describe('POST /api/users', () => {
it('should create a user with valid data', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
password: 'SecurePass123!'
};
const response = await request(app)
.post('/api/users')
.send(userData)
.set('Content-Type', 'application/json')
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.email).toBe(userData.email);
expect(response.body).not.toHaveProperty('password'); // Don't return password
});
it('should reject invalid email', async () => {
const response = await request(app)
.post('/api/users')
.send({
name: 'John',
email: 'invalid-email',
password: 'SecurePass123!'
})
.expect(400);
expect(response.body).toHaveProperty('error');
expect(response.body.error).toMatch(/email/i);
});
it('should reject weak password', async () => {
const response = await request(app)
.post('/api/users')
.send({
name: 'John',
email: 'john@example.com',
password: '123' // Too short
})
.expect(400);
expect(response.body).toHaveProperty('error');
expect(response.body.error).toMatch(/password/i);
});
});
describe('Authentication', () => {
let authToken;
beforeAll(async () => {
// Create a test user and get token
const response = await request(app)
.post('/api/login')
.send({
email: 'test@example.com',
password: 'TestPass123!'
});
authToken = response.body.token;
});
it('should access protected route with valid token', async () => {
const response = await request(app)
.get('/api/profile')
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body).toHaveProperty('user');
});
it('should reject access without token', async () => {
await request(app)
.get('/api/profile')
.expect(401);
});
it('should reject invalid token', async () => {
await request(app)
.get('/api/profile')
.set('Authorization', 'Bearer invalid-token')
.expect(401);
});
});
Testing Proxy Endpoints
const request = require('supertest');
const axios = require('axios');
const app = require('../server');
// Mock axios
jest.mock('axios');
describe('POST /api/proxy', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should proxy request successfully', async () => {
const mockData = {
results: [
{ id: 1, name: 'Result 1' },
{ id: 2, name: 'Result 2' }
]
};
axios.post.mockResolvedValue({
data: mockData,
status: 200
});
const response = await request(app)
.post('/api/proxy')
.send({ query: 'test' })
.expect(200);
expect(response.body).toEqual(mockData);
expect(axios.post).toHaveBeenCalledWith(
expect.any(String),
{ query: 'test' },
expect.any(Object)
);
});
it('should handle proxy errors', async () => {
axios.post.mockRejectedValue({
response: {
status: 500,
data: { error: 'Internal Server Error' }
}
});
const response = await request(app)
.post('/api/proxy')
.send({ query: 'test' })
.expect(500);
expect(response.body).toHaveProperty('error');
});
it('should handle network errors', async () => {
axios.post.mockRejectedValue(new Error('Network error'));
const response = await request(app)
.post('/api/proxy')
.send({ query: 'test' })
.expect(500);
expect(response.body).toHaveProperty('error');
});
it('should validate request before proxying', async () => {
const response = await request(app)
.post('/api/proxy')
.send({ invalid: 'data' })
.expect(400);
expect(response.body).toHaveProperty('error');
expect(axios.post).not.toHaveBeenCalled();
});
});
Mocking Strategies
Mocking External APIs
Mock entire module:
jest.mock('axios');
const axios = require('axios');
describe('External API calls', () => {
it('should fetch data from external API', async () => {
const mockData = { data: 'test' };
axios.get.mockResolvedValue({ data: mockData });
const result = await fetchExternalData();
expect(result).toEqual(mockData);
expect(axios.get).toHaveBeenCalledWith('https://api.example.com/data');
});
});
Mock specific functions:
const userService = require('../services/user');
jest.spyOn(userService, 'findById').mockResolvedValue({
id: 1,
name: 'Test User'
});
describe('User routes', () => {
it('should get user by id', async () => {
const response = await request(app)
.get('/api/users/1')
.expect(200);
expect(response.body.name).toBe('Test User');
expect(userService.findById).toHaveBeenCalledWith('1');
});
});
Manual mocks:
// __mocks__/axios.js
module.exports = {
get: jest.fn(() => Promise.resolve({ data: {} })),
post: jest.fn(() => Promise.resolve({ data: {} })),
put: jest.fn(() => Promise.resolve({ data: {} })),
delete: jest.fn(() => Promise.resolve({ data: {} }))
};
Mocking Database
// Mock database module
jest.mock('../db');
const db = require('../db');
describe('Database operations', () => {
beforeEach(() => {
db.query.mockClear();
});
it('should query users', async () => {
const mockUsers = [
{ id: 1, name: 'User 1' },
{ id: 2, name: 'User 2' }
];
db.query.mockResolvedValue({ rows: mockUsers });
const users = await User.findAll();
expect(users).toEqual(mockUsers);
expect(db.query).toHaveBeenCalledWith('SELECT * FROM users');
});
it('should handle database errors', async () => {
db.query.mockRejectedValue(new Error('Connection failed'));
await expect(User.findAll()).rejects.toThrow('Connection failed');
});
});
Mocking Environment Variables
describe('Environment configuration', () => {
const originalEnv = process.env;
beforeEach(() => {
jest.resetModules();
process.env = { ...originalEnv };
});
afterAll(() => {
process.env = originalEnv;
});
it('should use default port when PORT not set', () => {
delete process.env.PORT;
const config = require('../config');
expect(config.port).toBe(3000);
});
it('should use PORT from environment', () => {
process.env.PORT = '8080';
const config = require('../config');
expect(config.port).toBe(8080);
});
});
Unit vs Integration vs E2E Testing
Unit Tests
What: Test individual functions/modules in isolation
Example:
// validators.js
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
function isStrongPassword(password) {
return password.length >= 12 &&
/[A-Z]/.test(password) &&
/[a-z]/.test(password) &&
/[0-9]/.test(password) &&
/[^A-Za-z0-9]/.test(password);
}
module.exports = { isValidEmail, isStrongPassword };
// validators.test.js
const { isValidEmail, isStrongPassword } = require('./validators');
describe('Email validation', () => {
it('should accept valid email', () => {
expect(isValidEmail('test@example.com')).toBe(true);
});
it('should reject email without @', () => {
expect(isValidEmail('testexample.com')).toBe(false);
});
it('should reject email without domain', () => {
expect(isValidEmail('test@')).toBe(false);
});
it('should reject email with spaces', () => {
expect(isValidEmail('test @example.com')).toBe(false);
});
});
describe('Password validation', () => {
it('should accept strong password', () => {
expect(isStrongPassword('MyP@ssw0rd123!')).toBe(true);
});
it('should reject short password', () => {
expect(isStrongPassword('Short1!')).toBe(false);
});
it('should reject password without uppercase', () => {
expect(isStrongPassword('myp@ssw0rd123!')).toBe(false);
});
it('should reject password without special char', () => {
expect(isStrongPassword('MyPassword123')).toBe(false);
});
});
Integration Tests
What: Test multiple components working together
Example:
const request = require('supertest');
const app = require('../server');
const db = require('../db');
describe('User registration flow', () => {
beforeEach(async () => {
// Clean database before each test
await db.query('DELETE FROM users');
});
it('should register user and allow login', async () => {
// Register user
const registerResponse = await request(app)
.post('/api/register')
.send({
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
})
.expect(201);
expect(registerResponse.body).toHaveProperty('id');
// Login with registered credentials
const loginResponse = await request(app)
.post('/api/login')
.send({
email: 'test@example.com',
password: 'SecurePass123!'
})
.expect(200);
expect(loginResponse.body).toHaveProperty('token');
// Access protected route with token
const profileResponse = await request(app)
.get('/api/profile')
.set('Authorization', `Bearer ${loginResponse.body.token}`)
.expect(200);
expect(profileResponse.body.email).toBe('test@example.com');
});
});
End-to-End (E2E) Tests
What: Test complete user workflows from UI to database
Setup with Puppeteer:
npm install --save-dev puppeteer
Example:
const puppeteer = require('puppeteer');
describe('E2E: User registration', () => {
let browser;
let page;
beforeAll(async () => {
browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox']
});
page = await browser.newPage();
});
afterAll(async () => {
await browser.close();
});
it('should complete registration flow', async () => {
// Navigate to registration page
await page.goto('http://localhost:3000/register');
// Fill out form
await page.type('#email', 'test@example.com');
await page.type('#password', 'SecurePass123!');
await page.type('#confirmPassword', 'SecurePass123!');
// Submit form
await page.click('button[type="submit"]');
// Wait for redirect to dashboard
await page.waitForNavigation();
// Verify we're on dashboard
const url = page.url();
expect(url).toContain('/dashboard');
// Verify welcome message
const welcomeMessage = await page.$eval(
'.welcome',
el => el.textContent
);
expect(welcomeMessage).toContain('test@example.com');
});
});
Test Organization
Describe Blocks
describe('User API', () => {
describe('GET /api/users', () => {
it('should return all users', async () => {
// Test implementation
});
it('should support pagination', async () => {
// Test implementation
});
it('should support filtering', async () => {
// Test implementation
});
});
describe('POST /api/users', () => {
it('should create user with valid data', async () => {
// Test implementation
});
it('should reject duplicate email', async () => {
// Test implementation
});
});
describe('PUT /api/users/:id', () => {
it('should update user', async () => {
// Test implementation
});
it('should reject unauthorized update', async () => {
// Test implementation
});
});
});
Setup and Teardown
describe('Database tests', () => {
// Runs once before all tests in this describe block
beforeAll(async () => {
await db.connect();
});
// Runs once after all tests in this describe block
afterAll(async () => {
await db.disconnect();
});
// Runs before each test in this describe block
beforeEach(async () => {
await db.query('DELETE FROM users');
await db.query('INSERT INTO users (email) VALUES ($1)', ['test@example.com']);
});
// Runs after each test in this describe block
afterEach(async () => {
jest.clearAllMocks();
});
it('should find user', async () => {
const user = await User.findByEmail('test@example.com');
expect(user).toBeTruthy();
});
it('should delete user', async () => {
await User.deleteByEmail('test@example.com');
const user = await User.findByEmail('test@example.com');
expect(user).toBeNull();
});
});
Test Fixtures
// test/fixtures/users.js
module.exports = {
validUser: {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
},
adminUser: {
email: 'admin@example.com',
password: 'AdminPass123!',
name: 'Admin User',
role: 'admin'
},
invalidUsers: {
noEmail: {
password: 'SecurePass123!',
name: 'Test User'
},
weakPassword: {
email: 'test@example.com',
password: '123',
name: 'Test User'
}
}
};
// Usage in tests
const fixtures = require('./fixtures/users');
describe('User creation', () => {
it('should create valid user', async () => {
const response = await request(app)
.post('/api/users')
.send(fixtures.validUser)
.expect(201);
});
it('should reject user without email', async () => {
const response = await request(app)
.post('/api/users')
.send(fixtures.invalidUsers.noEmail)
.expect(400);
});
});
Async Testing
Testing Promises
describe('Async operations', () => {
it('should resolve with data', async () => {
const data = await fetchData();
expect(data).toBeDefined();
});
it('should reject with error', async () => {
await expect(fetchInvalidData()).rejects.toThrow('Not found');
});
// Alternative: using done callback
it('should fetch data (callback style)', (done) => {
fetchData()
.then(data => {
expect(data).toBeDefined();
done();
})
.catch(done);
});
});
Testing Callbacks
describe('Callback functions', () => {
it('should call callback with data', (done) => {
fetchDataWithCallback((err, data) => {
expect(err).toBeNull();
expect(data).toBeDefined();
done();
});
});
it('should call callback with error', (done) => {
fetchInvalidDataWithCallback((err, data) => {
expect(err).toBeTruthy();
expect(data).toBeUndefined();
done();
});
});
});
Code Coverage
Generating Coverage Reports
# Run tests with coverage
npm run test:coverage
# Coverage report output
----------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
----------|---------|----------|---------|---------|
All files | 85.5 | 78.3 | 91.2 | 85.1 |
server.js| 92.3 | 85.7 | 100 | 91.8 |
routes/ | 78.9 | 71.4 | 83.3 | 79.2 |
----------|---------|----------|---------|---------|
Coverage Configuration
// jest.config.js
module.exports = {
collectCoverageFrom: [
'src/**/*.js',
'!src/index.js', // Exclude entry point
'!src/**/*.test.js', // Exclude test files
'!src/**/__tests__/**' // Exclude test directories
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
},
// Per-file thresholds
'./src/critical-module.js': {
branches: 100,
functions: 100,
lines: 100,
statements: 100
}
},
coverageReporters: [
'text', // Terminal output
'html', // HTML report in coverage/
'lcov', // For CI tools
'json' // Machine-readable
]
};
Viewing HTML Coverage Report
npm run test:coverage
open coverage/index.html # macOS
xdg-open coverage/index.html # Linux
start coverage/index.html # Windows
Testing Best Practices
1. Naming Conventions
// GOOD - Descriptive test names
describe('User registration', () => {
it('should create user with valid email and password', () => {});
it('should reject registration with duplicate email', () => {});
it('should hash password before storing', () => {});
});
// BAD - Vague test names
describe('User', () => {
it('works', () => {});
it('test 1', () => {});
it('should not fail', () => {});
});
2. AAA Pattern (Arrange, Act, Assert)
it('should calculate total price with tax', () => {
// Arrange: Set up test data
const items = [
{ price: 10, quantity: 2 },
{ price: 5, quantity: 3 }
];
const taxRate = 0.1;
// Act: Perform the action
const total = calculateTotal(items, taxRate);
// Assert: Verify the result
expect(total).toBe(38.5); // (10*2 + 5*3) * 1.1
});
3. Test One Thing
// GOOD - Each test checks one behavior
it('should validate email format', () => {
expect(isValidEmail('test@example.com')).toBe(true);
});
it('should reject email without domain', () => {
expect(isValidEmail('test@')).toBe(false);
});
// BAD - Testing multiple things
it('should validate inputs', () => {
expect(isValidEmail('test@example.com')).toBe(true);
expect(isValidPassword('pass123')).toBe(false);
expect(isValidPhone('1234567890')).toBe(true);
});
4. Avoid Test Interdependence
// BAD - Tests depend on each other
let userId;
it('should create user', async () => {
const response = await createUser();
userId = response.id; // Other tests depend on this
});
it('should update user', async () => {
await updateUser(userId); // Fails if previous test fails
});
// GOOD - Each test is independent
describe('User operations', () => {
let userId;
beforeEach(async () => {
const user = await createUser();
userId = user.id;
});
it('should update user', async () => {
await updateUser(userId);
});
it('should delete user', async () => {
await deleteUser(userId);
});
});
5. Use Meaningful Assertions
// GOOD - Specific assertions
expect(response.status).toBe(200);
expect(response.body).toHaveProperty('users');
expect(response.body.users).toHaveLength(5);
expect(response.body.users[0]).toMatchObject({
id: expect.any(Number),
email: expect.stringMatching(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/)
});
// BAD - Vague assertions
expect(response).toBeTruthy();
expect(response.body).toBeDefined();
6. Test Edge Cases
describe('Division function', () => {
it('should divide positive numbers', () => {
expect(divide(10, 2)).toBe(5);
});
it('should handle negative numbers', () => {
expect(divide(-10, 2)).toBe(-5);
});
it('should handle zero numerator', () => {
expect(divide(0, 5)).toBe(0);
});
it('should throw error for division by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
it('should handle decimal results', () => {
expect(divide(5, 2)).toBe(2.5);
});
});
CI/CD Integration
GitHub Actions
# .github/workflows/test.yml
name: Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Generate coverage report
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage/coverage-final.json
fail_ci_if_error: true
npm Scripts for CI
{
"scripts": {
"test": "jest",
"test:ci": "jest --ci --coverage --maxWorkers=2",
"test:coverage": "jest --coverage",
"test:watch": "jest --watch"
}
}
Common Jest Matchers
Equality
expect(value).toBe(4); // Strict equality (===)
expect(value).toEqual({ a: 1 }); // Deep equality
expect(value).not.toBe(5); // Negation
Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
Numbers
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3.5);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4.5);
expect(value).toBeCloseTo(0.3); // Floating point
Strings
expect(string).toMatch(/pattern/);
expect(string).toMatch('substring');
expect(string).toContain('substring');
Arrays and Iterables
expect(array).toContain('item');
expect(array).toHaveLength(3);
expect(array).toEqual(expect.arrayContaining([1, 2]));
Objects
expect(object).toHaveProperty('key');
expect(object).toHaveProperty('key', value);
expect(object).toMatchObject({ a: 1, b: 2 });
expect(object).toEqual(expect.objectContaining({ a: 1 }));
Functions
expect(fn).toThrow();
expect(fn).toThrow('error message');
expect(fn).toThrow(Error);
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledWith(arg1, arg2);
expect(fn).toHaveBeenCalledTimes(3);
Testing Checklist
Unit Tests
- Test pure functions in isolation
- Test all code paths (happy path and error cases)
- Test edge cases and boundary conditions
- Mock external dependencies
- Achieve high code coverage (>80%)
Integration Tests
- Test API endpoints
- Test authentication/authorization
- Test database operations
- Test external API integration
- Test error handling
E2E Tests
- Test critical user flows
- Test form submissions
- Test navigation
- Test authentication flow
General
- Tests are fast (< 5 seconds for unit tests)
- Tests are independent (can run in any order)
- Tests are repeatable (same result every time)
- Tests have clear, descriptive names
- Setup and teardown properly implemented
- No hardcoded values (use constants/fixtures)
- CI/CD integration configured
Example Test Suite for Express API
const request = require('supertest');
const app = require('../server');
const db = require('../db');
describe('Express API Tests', () => {
// Setup: Connect to test database
beforeAll(async () => {
await db.connect(process.env.TEST_DATABASE_URL);
});
// Cleanup: Disconnect from database
afterAll(async () => {
await db.disconnect();
});
// Reset database before each test
beforeEach(async () => {
await db.query('DELETE FROM users');
});
describe('GET /api/health', () => {
it('should return health status', async () => {
const response = await request(app)
.get('/api/health')
.expect(200);
expect(response.body).toEqual({
status: 'ok',
timestamp: expect.any(Number)
});
});
});
describe('POST /api/users', () => {
it('should create user with valid data', async () => {
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(Number),
email: userData.email,
name: userData.name
});
expect(response.body).not.toHaveProperty('password');
});
it('should reject duplicate email', async () => {
const userData = {
email: 'test@example.com',
password: 'SecurePass123!',
name: 'Test User'
};
// Create first user
await request(app).post('/api/users').send(userData);
// Try to create duplicate
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(409);
expect(response.body.error).toMatch(/already exists/i);
});
it('should validate email format', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: 'invalid-email',
password: 'SecurePass123!',
name: 'Test'
})
.expect(400);
expect(response.body.error).toMatch(/email/i);
});
it('should enforce password requirements', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: 'test@example.com',
password: 'weak',
name: 'Test'
})
.expect(400);
expect(response.body.error).toMatch(/password/i);
});
});
describe('Authentication', () => {
let authToken;
const testUser = {
email: 'auth@example.com',
password: 'SecurePass123!',
name: 'Auth User'
};
beforeEach(async () => {
// Create user
await request(app).post('/api/users').send(testUser);
// Login and get token
const response = await request(app)
.post('/api/login')
.send({
email: testUser.email,
password: testUser.password
});
authToken = response.body.token;
});
it('should login with valid credentials', async () => {
const response = await request(app)
.post('/api/login')
.send({
email: testUser.email,
password: testUser.password
})
.expect(200);
expect(response.body).toHaveProperty('token');
});
it('should reject invalid credentials', async () => {
await request(app)
.post('/api/login')
.send({
email: testUser.email,
password: 'WrongPassword'
})
.expect(401);
});
it('should access protected route with token', async () => {
const response = await request(app)
.get('/api/profile')
.set('Authorization', `Bearer ${authToken}`)
.expect(200);
expect(response.body.email).toBe(testUser.email);
});
it('should reject access without token', async () => {
await request(app)
.get('/api/profile')
.expect(401);
});
});
});
Resources
- Jest Documentation: https://jestjs.io/docs/getting-started
- Supertest Documentation: https://github.com/ladjs/supertest
- Testing Best Practices: https://github.com/goldbergyoni/javascript-testing-best-practices
- Kent C. Dodds Testing Library: https://testing-library.com/
- Node.js Testing Best Practices: https://github.com/goldbergyoni/nodebestpractices#6-testing-best-practices
More from webdev70/hosting-google
google-cloud-build-expert
Expert knowledge of Google Cloud Build CI/CD pipelines including cloudbuild.yaml syntax, build steps, builders, substitution variables, triggers, secrets, artifact handling, and deployment to Cloud Run. Use when working with Cloud Build configurations, troubleshooting build pipelines, or deploying to Google Cloud Platform.
11frontend-api-integration
Expert knowledge of frontend JavaScript for API integration including fetch/axios patterns, async/await error handling, form validation and submission, pagination implementation, loading states, DOM manipulation, event listeners, query parameter building, and vanilla JS best practices. Use when working with public/script.js, adding UI features, debugging client-side API issues, implementing forms, or managing client-side state.
5express-nodejs-expert
Expert knowledge of Express.js and Node.js for building production-ready web applications and APIs. Covers middleware patterns, routing, async/await error handling, security, performance optimization, proxy patterns, static file serving, and production deployment. Use when working with server.js, adding routes, implementing middleware, debugging Express issues, or optimizing API endpoints.
5docker-containerization-expert
Expert knowledge of Docker containerization including Dockerfile best practices, docker-compose configuration, Alpine Linux specifics, multi-stage builds, security, health checks, and container optimization. Use when working with Dockerfile, docker-compose.yml, container builds, debugging container issues, or deploying to container platforms.
4