Testing | Fastify (2024)

Testing is one of the most important parts of developing an application. Fastifyis very flexible when it comes to testing and is compatible with most testingframeworks (such as Tap, which is used inthe examples below).

Application

Let's cd into a fresh directory called 'testing-example' and type npm init-y in our terminal.

Run npm i fastify && npm i tap pino-pretty -D

Separating concerns makes testing easy

First, we are going to separate our application code from our server code:

app.js:

'use strict'

const fastify = require('fastify')

function build(opts={}) {
const app = fastify(opts)
app.get('/', async function (request, reply) {
return { hello: 'world' }
})

return app
}

module.exports = build

server.js:

'use strict'

const server = require('./app')({
logger: {
level: 'info',
transport: {
target: 'pino-pretty'
}
}
})

server.listen({ port: 3000 }, (err, address) => {
if (err) {
server.log.error(err)
process.exit(1)
}
})

Benefits of using fastify.inject()

Fastify comes with built-in support for fake HTTP injection thanks tolight-my-request.

Before introducing any tests, we will use the .inject method to make a fakerequest to our route:

app.test.js:

'use strict'

const build = require('./app')

const test = async () => {
const app = build()

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

console.log('status code: ', response.statusCode)
console.log('body: ', response.body)
}
test()

First, our code will run inside an asynchronous function, giving us access toasync/await.

.inject ensures all registered plugins have booted up and our application isready to test. Finally, we pass the request method we want to use and a route.Using await we can store the response without a callback.

Run the test file in your terminal node app.test.js

status code: 200
body: {"hello":"world"}

Testing with HTTP injection

Now we can replace our console.log calls with actual tests!

In your package.json change the "test" script to:

"test": "tap --reporter=list --watch"

app.test.js:

'use strict'

const { test } = require('tap')
const build = require('./app')

test('requests the "/" route', async t => {
const app = build()

const response = await app.inject({
method: 'GET',
url: '/'
})
t.equal(response.statusCode, 200, 'returns a status code of 200')
})

Finally, run npm test in the terminal and see your test results!

The inject method can do much more than a simple GET request to a URL:

fastify.inject({
method: String,
url: String,
query: Object,
payload: Object,
headers: Object,
cookies: Object
}, (error, response) => {
// your tests
})

.inject methods can also be chained by omitting the callback function:

fastify
.inject()
.get('/')
.headers({ foo: 'bar' })
.query({ foo: 'bar' })
.end((err, res) => { // the .end call will trigger the request
console.log(res.payload)
})

or in the promisified version

fastify
.inject({
method: String,
url: String,
query: Object,
payload: Object,
headers: Object,
cookies: Object
})
.then(response => {
// your tests
})
.catch(err => {
// handle error
})

Async await is supported as well!

try {
const res = await fastify.inject({ method: String, url: String, payload: Object, headers: Object })
// your tests
} catch (err) {
// handle error
}

Another Example:

app.js

const Fastify = require('fastify')

function buildFastify () {
const fastify = Fastify()

fastify.get('/', function (request, reply) {
reply.send({ hello: 'world' })
})

return fastify
}

module.exports = buildFastify

test.js

const tap = require('tap')
const buildFastify = require('./app')

tap.test('GET `/` route', t => {
t.plan(4)

const fastify = buildFastify()

// At the end of your tests it is highly recommended to call `.close()`
// to ensure that all connections to external services get closed.
t.teardown(() => fastify.close())

fastify.inject({
method: 'GET',
url: '/'
}, (err, response) => {
t.error(err)
t.equal(response.statusCode, 200)
t.equal(response.headers['content-type'], 'application/json; charset=utf-8')
t.same(response.json(), { hello: 'world' })
})
})

Testing with a running server

Fastify can also be tested after starting the server with fastify.listen() orafter initializing routes and plugins with fastify.ready().

Example:

Uses app.js from the previous example.

test-listen.js (testing with undici)

const tap = require('tap')
const { Client } = require('undici')
const buildFastify = require('./app')

tap.test('should work with undici', async t => {
t.plan(2)

const fastify = buildFastify()

await fastify.listen()

const client = new Client(
'http://localhost:' + fastify.server.address().port, {
keepAliveTimeout: 10,
keepAliveMaxTimeout: 10
}
)

t.teardown(() => {
fastify.close()
client.close()
})

const response = await client.request({ method: 'GET', path: '/' })

t.equal(await response.body.text(), '{"hello":"world"}')
t.equal(response.statusCode, 200)
})

Alternatively, starting with Node.js 18,fetchmay be used without requiring any extra dependencies:

test-listen.js

const tap = require('tap')
const buildFastify = require('./app')

tap.test('should work with fetch', async t => {
t.plan(3)

const fastify = buildFastify()

t.teardown(() => fastify.close())

await fastify.listen()

const response = await fetch(
'http://localhost:' + fastify.server.address().port
)

t.equal(response.status, 200)
t.equal(
response.headers.get('content-type'),
'application/json; charset=utf-8'
)
t.has(await response.json(), { hello: 'world' })
})

test-ready.js (testing withSuperTest)

const tap = require('tap')
const supertest = require('supertest')
const buildFastify = require('./app')

tap.test('GET `/` route', async (t) => {
const fastify = buildFastify()

t.teardown(() => fastify.close())

await fastify.ready()

const response = await supertest(fastify.server)
.get('/')
.expect(200)
.expect('Content-Type', 'application/json; charset=utf-8')
t.same(response.body, { hello: 'world' })
})

How to inspect tap tests

  1. Isolate your test by passing the {only: true} option
test('should ...', {only: true}, t => ...)
  1. Run tap using npx
> npx tap -O -T --node-arg=--inspect-brk test/<test-file.test.js>
  • -O specifies to run tests with the only option enabled
  • -T specifies not to timeout (while you're debugging)
  • --node-arg=--inspect-brk will launch the node debugger
  1. In VS Code, create and launch a Node.js: Attach debug configuration. Nomodification should be necessary.

Now you should be able to step through your test file (and the rest ofFastify) in your code editor.

Plugins

Let's cd into a fresh directory called 'testing-plugin-example' and type npm init-y in our terminal.

Run npm i fastify fastify-plugin && npm i tap -D

plugin/myFirstPlugin.js:

const fP = require("fastify-plugin")

async function myPlugin(fastify, options) {
fastify.decorateRequest("helloRequest", "Hello World")
fastify.decorate("helloInstance", "Hello Fastify Instance")
}

module.exports = fP(myPlugin)

A basic example of a Plugin. See Plugin Guide

test/myFirstPlugin.test.js:

const Fastify = require("fastify");
const tap = require("tap");
const myPlugin = require("../plugin/myFirstPlugin");

tap.test("Test the Plugin Route", async t => {
// Create a mock fastify application to test the plugin
const fastify = Fastify()

fastify.register(myPlugin)

// Add an endpoint of your choice
fastify.get("/", async (request, reply) => {
return ({ message: request.helloRequest })
})

// Use fastify.inject to fake a HTTP Request
const fastifyResponse = await fastify.inject({
method: "GET",
url: "/"
})

console.log('status code: ', fastifyResponse.statusCode)
console.log('body: ', fastifyResponse.body)
})

Learn more about fastify.inject().Run the test file in your terminal node test/myFirstPlugin.test.js

status code: 200
body: {"message":"Hello World"}

Now we can replace our console.log calls with actual tests!

In your package.json change the "test" script to:

"test": "tap --reporter=list --watch"

Create the tap test for the endpoint.

test/myFirstPlugin.test.js:

const Fastify = require("fastify");
const tap = require("tap");
const myPlugin = require("../plugin/myFirstPlugin");

tap.test("Test the Plugin Route", async t => {
// Specifies the number of test
t.plan(2)

const fastify = Fastify()

fastify.register(myPlugin)

fastify.get("/", async (request, reply) => {
return ({ message: request.helloRequest })
})

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

t.equal(fastifyResponse.statusCode, 200)
t.same(JSON.parse(fastifyResponse.body), { message: "Hello World" })
})

Finally, run npm test in the terminal and see your test results!

Test the .decorate() and .decorateRequest().

test/myFirstPlugin.test.js:

const Fastify = require("fastify");
const tap = require("tap");
const myPlugin = require("../plugin/myFirstPlugin");

tap.test("Test the Plugin Route", async t => {
t.plan(5)
const fastify = Fastify()

fastify.register(myPlugin)

fastify.get("/", async (request, reply) => {
// Testing the fastify decorators
t.not(request.helloRequest, null)
t.ok(request.helloRequest, "Hello World")
t.ok(fastify.helloInstance, "Hello Fastify Instance")
return ({ message: request.helloRequest })
})

const fastifyResponse = await fastify.inject({
method: "GET",
url: "/"
})
t.equal(fastifyResponse.statusCode, 200)
t.same(JSON.parse(fastifyResponse.body), { message: "Hello World" })
})
Testing | Fastify (2024)
Top Articles
Ritual Casting in Dungeons & Dragons 5e Explained
SMS sender ID: What it is, why it matters, and how to choose yours
Funny Roblox Id Codes 2023
Devotion Showtimes Near Xscape Theatres Blankenbaker 16
Po Box 7250 Sioux Falls Sd
Fat Hog Prices Today
Week 2 Defense (DEF) Streamers, Starters & Rankings: 2024 Fantasy Tiers, Rankings
855-392-7812
Stadium Seats Near Me
Fully Enclosed IP20 Interface Modules To Ensure Safety In Industrial Environment
Es.cvs.com/Otchs/Devoted
Obituaries
Tanger Outlets Sevierville Directory Map
Fallout 4 Pipboy Upgrades
83600 Block Of 11Th Street East Palmdale Ca
shopping.drugsourceinc.com/imperial | Imperial Health TX AZ
Find The Eagle Hunter High To The East
3656 Curlew St
Skylar Vox Bra Size
Vrachtwagens in Nederland kopen - gebruikt en nieuw - TrucksNL
Nurse Logic 2.0 Testing And Remediation Advanced Test
Robert Deshawn Swonger Net Worth
Quest: Broken Home | Sal's Realm of RuneScape
Rufus Benton "Bent" Moulds Jr. Obituary 2024 - Webb & Stephens Funeral Homes
Ecampus Scps Login
Baldur's Gate 3: Should You Obey Vlaakith?
Reser Funeral Home Obituaries
Celina Powell Lil Meech Video: A Controversial Encounter Shakes Social Media - Video Reddit Trend
Soul Eater Resonance Wavelength Tier List
Random Bibleizer
Cowboy Pozisyon
3 Ways to Drive Employee Engagement with Recognition Programs | UKG
His Only Son Showtimes Near Marquee Cinemas - Wakefield 12
1964 Impala For Sale Craigslist
FSA Award Package
Lininii
Restaurants Near Calvary Cemetery
Calculator Souo
Emily Katherine Correro
404-459-1280
T&J Agnes Theaters
Can You Buy Pedialyte On Food Stamps
State Legislatures Icivics Answer Key
Nba Props Covers
How to Quickly Detect GI Stasis in Rabbits (and what to do about it) | The Bunny Lady
Tgirls Philly
Sig Mlok Bayonet Mount
Ohio Road Construction Map
Streameast Io Soccer
Costner-Maloy Funeral Home Obituaries
Latest Posts
Article information

Author: Kareem Mueller DO

Last Updated:

Views: 5741

Rating: 4.6 / 5 (46 voted)

Reviews: 93% of readers found this page helpful

Author information

Name: Kareem Mueller DO

Birthday: 1997-01-04

Address: Apt. 156 12935 Runolfsdottir Mission, Greenfort, MN 74384-6749

Phone: +16704982844747

Job: Corporate Administration Planner

Hobby: Mountain biking, Jewelry making, Stone skipping, Lacemaking, Knife making, Scrapbooking, Letterboxing

Introduction: My name is Kareem Mueller DO, I am a vivacious, super, thoughtful, excited, handsome, beautiful, combative person who loves writing and wants to share my knowledge and understanding with you.