CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-fastify

Fast and low overhead web framework for Node.js with powerful plugin architecture

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

testing.mddocs/

Testing Utilities

Built-in testing support with HTTP injection capabilities for unit and integration testing.

Capabilities

HTTP Injection

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' }

Testing with Test Frameworks

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 });
  });
});

Advanced Testing Scenarios

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');
});

Test Utilities and Helpers

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);
});

Injection Chain

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();

Mock and Stub Testing

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);
  });
});

Performance Testing

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`);
});

docs

content-parsing.md

decoration.md

error-handling.md

hooks.md

index.md

plugins.md

routing.md

schema.md

server-lifecycle.md

testing.md

tile.json