Built-in testing support with HTTP injection capabilities for unit and integration testing.
Simulate HTTP requests without starting a server using light-my-request.
/**
* Inject HTTP request for testing (Promise-based)
* @param options - Request options or URL string
* @returns Promise resolving to response object
*/
inject(options: InjectOptions | string): Promise<LightMyRequestResponse>;
/**
* Inject HTTP request with callback
* @param options - Request options or URL string
* @param callback - Callback function receiving error and response
*/
inject(options: InjectOptions | string, callback: (err: Error, response: LightMyRequestResponse) => void): void;
/**
* Get injection chain for multiple requests
* @returns Chain object for chaining multiple requests
*/
inject(): LightMyRequestChain;interface InjectOptions {
method?: string;
url: string;
headers?: object;
payload?: string | object | Buffer;
query?: object;
cookies?: object;
auth?: {
strategy: string;
credentials: any;
};
validate?: boolean;
remoteAddress?: string;
server?: object;
}
interface LightMyRequestResponse {
statusCode: number;
statusMessage: string;
headers: object;
payload: string;
body: string;
json(): any;
cookies: Array<{ name: string; value: string; [key: string]: any }>;
}Basic Testing Examples:
const fastify = require('fastify')();
// Add routes
fastify.get('/hello', async () => ({ hello: 'world' }));
fastify.post('/users', async (request) => ({
id: 1,
...request.body
}));
// Test GET request
const response = await fastify.inject({
method: 'GET',
url: '/hello'
});
console.log(response.statusCode); // 200
console.log(response.json()); // { hello: 'world' }
// Test POST request
const postResponse = await fastify.inject({
method: 'POST',
url: '/users',
payload: { name: 'John', email: 'john@example.com' }
});
console.log(postResponse.statusCode); // 200
console.log(postResponse.json()); // { id: 1, name: 'John', email: 'john@example.com' }Integration with popular testing frameworks.
Jest Examples:
const { test } = require('@jest/globals');
describe('User API', () => {
let app;
beforeAll(async () => {
app = require('./app'); // Your Fastify app
await app.ready();
});
afterAll(async () => {
await app.close();
});
test('GET /users returns user list', async () => {
const response = await app.inject({
method: 'GET',
url: '/users'
});
expect(response.statusCode).toBe(200);
expect(response.json()).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(Number),
name: expect.any(String)
})
])
);
});
test('POST /users creates new user', async () => {
const userData = { name: 'Jane', email: 'jane@example.com' };
const response = await app.inject({
method: 'POST',
url: '/users',
payload: userData
});
expect(response.statusCode).toBe(201);
expect(response.json()).toMatchObject(userData);
});
test('GET /users/:id returns specific user', async () => {
const response = await app.inject({
method: 'GET',
url: '/users/1'
});
expect(response.statusCode).toBe(200);
expect(response.json()).toHaveProperty('id', 1);
});
test('returns 404 for non-existent user', async () => {
const response = await app.inject({
method: 'GET',
url: '/users/999'
});
expect(response.statusCode).toBe(404);
expect(response.json()).toHaveProperty('error');
});
});Tap Examples:
const tap = require('tap');
tap.test('User routes', async (t) => {
const app = require('./app');
t.teardown(() => app.close());
await t.test('GET /users', async (t) => {
const response = await app.inject({
method: 'GET',
url: '/users'
});
t.equal(response.statusCode, 200);
t.type(response.json(), 'object');
});
await t.test('POST /users validation', async (t) => {
const response = await app.inject({
method: 'POST',
url: '/users',
payload: { name: '' } // Invalid data
});
t.equal(response.statusCode, 400);
t.match(response.json(), { error: /validation/i });
});
});Complex testing scenarios and patterns.
Authentication Testing:
// Helper function for authenticated requests
async function authenticatedRequest(app, options) {
// Get auth token
const authResponse = await app.inject({
method: 'POST',
url: '/auth/login',
payload: { username: 'test', password: 'test123' }
});
const { token } = authResponse.json();
// Make authenticated request
return app.inject({
...options,
headers: {
...options.headers,
authorization: `Bearer ${token}`
}
});
}
test('protected route requires authentication', async () => {
// Unauthenticated request
const unauthedResponse = await app.inject({
method: 'GET',
url: '/protected'
});
expect(unauthedResponse.statusCode).toBe(401);
// Authenticated request
const authedResponse = await authenticatedRequest(app, {
method: 'GET',
url: '/protected'
});
expect(authedResponse.statusCode).toBe(200);
});Cookie Testing:
test('session cookie handling', async () => {
// Login and get session cookie
const loginResponse = await app.inject({
method: 'POST',
url: '/login',
payload: { username: 'test', password: 'test123' }
});
const sessionCookie = loginResponse.cookies.find(c => c.name === 'session');
// Use session cookie in subsequent request
const profileResponse = await app.inject({
method: 'GET',
url: '/profile',
cookies: { session: sessionCookie.value }
});
expect(profileResponse.statusCode).toBe(200);
});File Upload Testing:
test('file upload', async () => {
const FormData = require('form-data');
const form = new FormData();
form.append('file', Buffer.from('test file content'), 'test.txt');
form.append('description', 'Test file');
const response = await app.inject({
method: 'POST',
url: '/upload',
headers: form.getHeaders(),
payload: form
});
expect(response.statusCode).toBe(201);
expect(response.json()).toHaveProperty('fileId');
});Common testing utilities and helper functions.
// Test data factory
function createTestUser(overrides = {}) {
return {
name: 'Test User',
email: 'test@example.com',
age: 25,
...overrides
};
}
// Database cleanup helper
async function cleanupDatabase() {
await db.query('DELETE FROM users WHERE email LIKE "test%"');
await db.query('DELETE FROM posts WHERE title LIKE "Test%"');
}
// Response assertion helpers
function expectSuccessResponse(response, expectedData) {
expect(response.statusCode).toBe(200);
expect(response.json()).toMatchObject({
success: true,
data: expectedData
});
}
function expectErrorResponse(response, statusCode, errorMessage) {
expect(response.statusCode).toBe(statusCode);
expect(response.json()).toMatchObject({
success: false,
error: expect.stringContaining(errorMessage)
});
}
// Usage in tests
test('user creation success', async () => {
const userData = createTestUser();
const response = await app.inject({
method: 'POST',
url: '/users',
payload: userData
});
expectSuccessResponse(response, userData);
});Chain multiple requests for complex testing scenarios.
interface LightMyRequestChain {
get(url: string): LightMyRequestChain;
post(url: string, payload?: any): LightMyRequestChain;
put(url: string, payload?: any): LightMyRequestChain;
delete(url: string): LightMyRequestChain;
headers(headers: object): LightMyRequestChain;
query(query: object): LightMyRequestChain;
end(callback: (err, response) => void): void;
end(): Promise<LightMyRequestResponse>;
}Chain Examples:
// Chain requests
const response = await app
.inject()
.get('/users/1')
.headers({ 'x-custom-header': 'value' })
.end();
// Chain with query parameters
const searchResponse = await app
.inject()
.get('/users')
.query({ page: 2, limit: 10 })
.end();
// Complex chain
const chainResponse = await app
.inject()
.post('/auth/login')
.payload({ username: 'test', password: 'test123' })
.end();
const token = chainResponse.json().token;
const profileResponse = await app
.inject()
.get('/profile')
.headers({ authorization: `Bearer ${token}` })
.end();Testing with mocked dependencies.
const sinon = require('sinon');
describe('User service integration', () => {
let app, dbStub;
beforeEach(async () => {
app = require('./app');
// Stub database calls
dbStub = sinon.stub(app.db, 'query');
await app.ready();
});
afterEach(async () => {
dbStub.restore();
await app.close();
});
test('handles database error gracefully', async () => {
// Mock database error
dbStub.rejects(new Error('Database connection failed'));
const response = await app.inject({
method: 'GET',
url: '/users'
});
expect(response.statusCode).toBe(500);
expect(response.json()).toHaveProperty('error');
});
test('returns mocked user data', async () => {
// Mock successful database response
dbStub.resolves([
{ id: 1, name: 'Mock User', email: 'mock@example.com' }
]);
const response = await app.inject({
method: 'GET',
url: '/users'
});
expect(response.statusCode).toBe(200);
expect(response.json()).toHaveLength(1);
expect(dbStub.calledWith('SELECT * FROM users')).toBe(true);
});
});Basic performance testing with injection.
test('endpoint performance', async () => {
const iterations = 100;
const responses = [];
const startTime = Date.now();
// Run multiple requests
for (let i = 0; i < iterations; i++) {
const response = await app.inject({
method: 'GET',
url: '/users'
});
responses.push(response);
}
const endTime = Date.now();
const avgResponseTime = (endTime - startTime) / iterations;
// All requests should succeed
responses.forEach(response => {
expect(response.statusCode).toBe(200);
});
// Performance assertion
expect(avgResponseTime).toBeLessThan(100); // < 100ms average
console.log(`Average response time: ${avgResponseTime}ms`);
});