Back to Repositories

Testing HTTP 500 Error Handler Implementation in Fastify

This test suite validates error handling and 500 status code responses in Fastify applications. It covers both default and custom error handlers, encapsulated error handling, and interaction with hooks.

Test Coverage Overview

The test suite provides comprehensive coverage of HTTP 500 error scenarios in Fastify.

Key areas tested include:
  • Default 500 error responses
  • Custom error handler implementation
  • Encapsulated error handling within plugins
  • Error handling with middleware hooks
  • Error handler binding restrictions

Implementation Analysis

The testing approach uses Node’s built-in test framework with Fastify’s injection mechanism for HTTP simulation. Tests employ async/await and callback patterns to verify error handling behavior.

Technical patterns include:
  • Error message and status code validation
  • Content-type header verification
  • Response payload structure checking
  • Hook execution order verification

Technical Details

Testing tools and configuration:
  • Node.js test runner (node:test)
  • Fastify injection API for request simulation
  • Custom error handler implementations
  • Hook middleware integration
  • Assertion methods for error validation

Best Practices Demonstrated

The test suite exemplifies robust error handling testing practices.

Notable practices include:
  • Isolated test cases with proper cleanup
  • Comprehensive assertion coverage
  • Error scenario simulation
  • Plugin encapsulation testing
  • Middleware hook integration verification

fastify/fastify

test/500s.test.js

            
'use strict'

const { test } = require('node:test')
const Fastify = require('..')
const symbols = require('../lib/symbols.js')

test('default 500', (t, done) => {
  t.plan(4)

  const fastify = Fastify()
  t.after(() => fastify.close())

  fastify.get('/', function (req, reply) {
    reply.send(new Error('kaboom'))
  })

  fastify.inject({
    method: 'GET',
    url: '/'
  }, (err, res) => {
    t.assert.ifError(err)
    t.assert.strictEqual(res.statusCode, 500)
    t.assert.strictEqual(res.headers['content-type'], 'application/json; charset=utf-8')
    t.assert.deepStrictEqual(JSON.parse(res.payload), {
      error: 'Internal Server Error',
      message: 'kaboom',
      statusCode: 500
    })
    done()
  })
})

test('custom 500', (t, done) => {
  t.plan(6)

  const fastify = Fastify()
  t.after(() => fastify.close())

  fastify.get('/', function (req, reply) {
    reply.send(new Error('kaboom'))
  })

  fastify.setErrorHandler(function (err, request, reply) {
    t.assert.ok(typeof request === 'object')
    t.assert.ok(request instanceof fastify[symbols.kRequest].parent)
    reply
      .code(500)
      .type('text/plain')
      .send('an error happened: ' + err.message)
  })

  fastify.inject({
    method: 'GET',
    url: '/'
  }, (err, res) => {
    t.assert.ifError(err)
    t.assert.strictEqual(res.statusCode, 500)
    t.assert.strictEqual(res.headers['content-type'], 'text/plain')
    t.assert.deepStrictEqual(res.payload.toString(), 'an error happened: kaboom')
    done()
  })
})

test('encapsulated 500', async t => {
  t.plan(8)

  const fastify = Fastify()
  t.after(() => fastify.close())

  fastify.get('/', function (req, reply) {
    reply.send(new Error('kaboom'))
  })

  fastify.register(function (f, opts, done) {
    f.get('/', function (req, reply) {
      reply.send(new Error('kaboom'))
    })

    f.setErrorHandler(function (err, request, reply) {
      t.assert.ok(typeof request === 'object')
      t.assert.ok(request instanceof fastify[symbols.kRequest].parent)
      reply
        .code(500)
        .type('text/plain')
        .send('an error happened: ' + err.message)
    })

    done()
  }, { prefix: 'test' })

  {
    const response = await fastify.inject({
      method: 'GET',
      url: '/test'
    })

    t.assert.strictEqual(response.statusCode, 500)
    t.assert.strictEqual(response.headers['content-type'], 'text/plain')
    t.assert.deepStrictEqual(response.payload.toString(), 'an error happened: kaboom')
  }

  {
    const response = await fastify.inject({
      method: 'GET',
      url: '/'
    })

    t.assert.strictEqual(response.statusCode, 500)
    t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8')
    t.assert.deepStrictEqual(JSON.parse(response.payload), {
      error: 'Internal Server Error',
      message: 'kaboom',
      statusCode: 500
    })
  }
})

test('custom 500 with hooks', (t, done) => {
  t.plan(7)

  const fastify = Fastify()
  t.after(() => fastify.close())

  fastify.get('/', function (req, reply) {
    reply.send(new Error('kaboom'))
  })

  fastify.setErrorHandler(function (err, request, reply) {
    reply
      .code(500)
      .type('text/plain')
      .send('an error happened: ' + err.message)
  })

  fastify.addHook('onSend', (req, res, payload, done) => {
    t.assert.ok('called', 'onSend')
    done()
  })
  fastify.addHook('onRequest', (req, res, done) => {
    t.assert.ok('called', 'onRequest')
    done()
  })
  fastify.addHook('onResponse', (request, reply, done) => {
    t.assert.ok('called', 'onResponse')
    done()
  })

  fastify.inject({
    method: 'GET',
    url: '/'
  }, (err, res) => {
    t.assert.ifError(err)
    t.assert.strictEqual(res.statusCode, 500)
    t.assert.strictEqual(res.headers['content-type'], 'text/plain')
    t.assert.deepStrictEqual(res.payload.toString(), 'an error happened: kaboom')
    done()
  })
})

test('cannot set errorHandler after binding', (t, done) => {
  t.plan(2)

  const fastify = Fastify()
  t.after(() => fastify.close())

  fastify.listen({ port: 0 }, err => {
    t.assert.ifError(err)

    try {
      fastify.setErrorHandler(() => { })
      t.assert.fail()
    } catch (e) {
      t.assert.ok(true)
    } finally {
      done()
    }
  })
})

test('cannot set childLoggerFactory after binding', (t, done) => {
  t.plan(2)

  const fastify = Fastify()
  t.after(() => fastify.close())

  fastify.listen({ port: 0 }, err => {
    t.assert.ifError(err)

    try {
      fastify.setChildLoggerFactory(() => { })
      t.assert.fail()
    } catch (e) {
      t.assert.ok(true)
    } finally {
      done()
    }
  })
})

test('catch synchronous errors', (t, done) => {
  t.plan(3)

  const fastify = Fastify()
  t.after(() => fastify.close())

  fastify.setErrorHandler((_, req, reply) => {
    throw new Error('kaboom2')
  })

  fastify.post('/', function (req, reply) {
    reply.send(new Error('kaboom'))
  })

  fastify.inject({
    method: 'POST',
    url: '/',
    headers: {
      'Content-Type': 'application/json'
    },
    payload: JSON.stringify({ hello: 'world' }).substring(0, 5)
  }, (err, res) => {
    t.assert.ifError(err)
    t.assert.strictEqual(res.statusCode, 500)
    t.assert.deepStrictEqual(res.json(), {
      error: 'Internal Server Error',
      message: 'kaboom2',
      statusCode: 500
    })
    done()
  })
})