strong-tie/inbound-calls
0
1<h1 style="text-align: center;">Fastify</h1>2 3# Testing4<a id="testing"></a>5 6Testing is one of the most important parts of developing an application. Fastify7is very flexible when it comes to testing and is compatible with most testing8frameworks (such as [Node Test Runner](https://nodejs.org/api/test.html),9which is used in the examples below).10 11## Application12 13Let's `cd` into a fresh directory called 'testing-example' and type `npm init14-y` in our terminal.15 16Run `npm i fastify && npm i pino-pretty -D`17 18### Separating concerns makes testing easy19 20First, we are going to separate our application code from our server code:21 22**app.js**:23 24```js25'use strict'26 27const fastify = require('fastify')28 29function build(opts={}) {30 const app = fastify(opts)31 app.get('/', async function (request, reply) {32 return { hello: 'world' }33 })34 35 return app36}37 38module.exports = build39```40 41**server.js**:42 43```js44'use strict'45 46const server = require('./app')({47 logger: {48 level: 'info',49 transport: {50 target: 'pino-pretty'51 }52 }53})54 55server.listen({ port: 3000 }, (err, address) => {56 if (err) {57 server.log.error(err)58 process.exit(1)59 }60})61```62 63### Benefits of using fastify.inject()64 65Fastify comes with built-in support for fake HTTP injection thanks to66[`light-my-request`](https://github.com/fastify/light-my-request).67 68Before introducing any tests, we will use the `.inject` method to make a fake69request to our route:70 71**app.test.js**:72 73```js74'use strict'75 76const build = require('./app')77 78const test = async () => {79 const app = build()80 81 const response = await app.inject({82 method: 'GET',83 url: '/'84 })85 86 console.log('status code: ', response.statusCode)87 console.log('body: ', response.body)88}89test()90```91 92First, our code will run inside an asynchronous function, giving us access to93async/await.94 95`.inject` ensures all registered plugins have booted up and our application is96ready to test. Finally, we pass the request method we want to use and a route.97Using await we can store the response without a callback.98 99 100 101Run the test file in your terminal `node app.test.js`102 103```sh104status code: 200105body: {"hello":"world"}106```107 108 109 110### Testing with HTTP injection111 112Now we can replace our `console.log` calls with actual tests!113 114In your `package.json` change the "test" script to:115 116`"test": "node --test --watch"`117 118**app.test.js**:119 120```js121'use strict'122 123const { test } = require('node:test')124const build = require('./app')125 126test('requests the "/" route', async t => {127 t.plan(1)128 const app = build()129 130 const response = await app.inject({131 method: 'GET',132 url: '/'133 })134 t.assert.strictEqual(response.statusCode, 200, 'returns a status code of 200')135})136```137 138Finally, run `npm test` in the terminal and see your test results!139 140The `inject` method can do much more than a simple GET request to a URL:141```js142fastify.inject({143 method: String,144 url: String,145 query: Object,146 payload: Object,147 headers: Object,148 cookies: Object149}, (error, response) => {150 // your tests151})152```153 154`.inject` methods can also be chained by omitting the callback function:155 156```js157fastify158 .inject()159 .get('/')160 .headers({ foo: 'bar' })161 .query({ foo: 'bar' })162 .end((err, res) => { // the .end call will trigger the request163 console.log(res.payload)164 })165```166 167or in the promisified version168 169```js170fastify171 .inject({172 method: String,173 url: String,174 query: Object,175 payload: Object,176 headers: Object,177 cookies: Object178 })179 .then(response => {180 // your tests181 })182 .catch(err => {183 // handle error184 })185```186 187Async await is supported as well!188```js189try {190 const res = await fastify.inject({ method: String, url: String, payload: Object, headers: Object })191 // your tests192} catch (err) {193 // handle error194}195```196 197#### Another Example:198 199**app.js**200```js201const Fastify = require('fastify')202 203function buildFastify () {204 const fastify = Fastify()205 206 fastify.get('/', function (request, reply) {207 reply.send({ hello: 'world' })208 })209 210 return fastify211}212 213module.exports = buildFastify214```215 216**test.js**217```js218const { test } = require('node:test')219const buildFastify = require('./app')220 221test('GET `/` route', t => {222 t.plan(4)223 224 const fastify = buildFastify()225 226 // At the end of your tests it is highly recommended to call `.close()`227 // to ensure that all connections to external services get closed.228 t.after(() => fastify.close())229 230 fastify.inject({231 method: 'GET',232 url: '/'233 }, (err, response) => {234 t.assert.ifError(err)235 t.assert.strictEqual(response.statusCode, 200)236 t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8')237 t.assert.deepStrictEqual(response.json(), { hello: 'world' })238 })239})240```241 242### Testing with a running server243Fastify can also be tested after starting the server with `fastify.listen()` or244after initializing routes and plugins with `fastify.ready()`.245 246#### Example:247 248Uses **app.js** from the previous example.249 250**test-listen.js** (testing with [`undici`](https://www.npmjs.com/package/undici))251```js252const { test } = require('node:test')253const { Client } = require('undici')254const buildFastify = require('./app')255 256test('should work with undici', async t => {257 t.plan(2)258 259 const fastify = buildFastify()260 261 await fastify.listen()262 263 const client = new Client(264 'http://localhost:' + fastify.server.address().port, {265 keepAliveTimeout: 10,266 keepAliveMaxTimeout: 10267 }268 )269 270 t.after(() => {271 fastify.close()272 client.close()273 })274 275 const response = await client.request({ method: 'GET', path: '/' })276 277 t.assert.strictEqual(await response.body.text(), '{"hello":"world"}')278 t.assert.strictEqual(response.statusCode, 200)279})280```281 282Alternatively, starting with Node.js 18,283[`fetch`](https://nodejs.org/docs/latest-v18.x/api/globals.html#fetch)284may be used without requiring any extra dependencies:285 286**test-listen.js**287```js288const { test } = require('node:test')289const buildFastify = require('./app')290 291test('should work with fetch', async t => {292 t.plan(3)293 294 const fastify = buildFastify()295 296 t.after(() => fastify.close())297 298 await fastify.listen()299 300 const response = await fetch(301 'http://localhost:' + fastify.server.address().port302 )303 304 t.assert.strictEqual(response.status, 200)305 t.assert.strictEqual(306 response.headers.get('content-type'),307 'application/json; charset=utf-8'308 )309 const jsonResult = await response.json()310 t.assert.strictEqual(jsonResult.hello, 'world')311})312```313 314**test-ready.js** (testing with315[`SuperTest`](https://www.npmjs.com/package/supertest))316```js317const { test } = require('node:test')318const supertest = require('supertest')319const buildFastify = require('./app')320 321test('GET `/` route', async (t) => {322 const fastify = buildFastify()323 324 t.after(() => fastify.close())325 326 await fastify.ready()327 328 const response = await supertest(fastify.server)329 .get('/')330 .expect(200)331 .expect('Content-Type', 'application/json; charset=utf-8')332 t.assert.deepStrictEqual(response.body, { hello: 'world' })333})334```335 336### How to inspect node tests3371. Isolate your test by passing the `{only: true}` option338```javascript339test('should ...', {only: true}, t => ...)340```3412. Run `node --test`342```bash343> node --test --test-only --node-arg=--inspect-brk test/<test-file.test.js>344```345- `--test-only` specifies to run tests with the `only` option enabled346- `--node-arg=--inspect-brk` will launch the node debugger3473. In VS Code, create and launch a `Node.js: Attach` debug configuration. No348 modification should be necessary.349 350Now you should be able to step through your test file (and the rest of351`Fastify`) in your code editor.352 353 354 355## Plugins356Let's `cd` into a fresh directory called 'testing-plugin-example' and type `npm init357-y` in our terminal.358 359Run `npm i fastify fastify-plugin`360 361**plugin/myFirstPlugin.js**:362 363```js364const fP = require("fastify-plugin")365 366async function myPlugin(fastify, options) {367 fastify.decorateRequest("helloRequest", "Hello World")368 fastify.decorate("helloInstance", "Hello Fastify Instance")369}370 371module.exports = fP(myPlugin)372```373 374A basic example of a Plugin. See [Plugin Guide](./Plugins-Guide.md)375 376**test/myFirstPlugin.test.js**:377 378```js379const Fastify = require("fastify");380const { test } = require("node:test");381const myPlugin = require("../plugin/myFirstPlugin");382 383test("Test the Plugin Route", async t => {384 // Create a mock fastify application to test the plugin385 const fastify = Fastify()386 387 fastify.register(myPlugin)388 389 // Add an endpoint of your choice390 fastify.get("/", async (request, reply) => {391 return ({ message: request.helloRequest })392 })393 394 // Use fastify.inject to fake a HTTP Request395 const fastifyResponse = await fastify.inject({396 method: "GET",397 url: "/"398 })399 400 console.log('status code: ', fastifyResponse.statusCode)401 console.log('body: ', fastifyResponse.body)402})403```404Learn more about [```fastify.inject()```](#benefits-of-using-fastifyinject).405Run the test file in your terminal `node test/myFirstPlugin.test.js`406 407```sh408status code: 200409body: {"message":"Hello World"}410```411 412Now we can replace our `console.log` calls with actual tests!413 414In your `package.json` change the "test" script to:415 416`"test": "node --test --watch"`417 418Create the test for the endpoint.419 420**test/myFirstPlugin.test.js**:421 422```js423const Fastify = require("fastify");424const { test } = require("node:test");425const myPlugin = require("../plugin/myFirstPlugin");426 427test("Test the Plugin Route", async t => {428 // Specifies the number of test429 t.plan(2)430 431 const fastify = Fastify()432 433 fastify.register(myPlugin)434 435 fastify.get("/", async (request, reply) => {436 return ({ message: request.helloRequest })437 })438 439 const fastifyResponse = await fastify.inject({440 method: "GET",441 url: "/"442 })443 444 t.assert.strictEqual(fastifyResponse.statusCode, 200)445 t.assert.deepStrictEqual(JSON.parse(fastifyResponse.body), { message: "Hello World" })446})447```448 449Finally, run `npm test` in the terminal and see your test results!450 451Test the ```.decorate()``` and ```.decorateRequest()```.452 453**test/myFirstPlugin.test.js**:454 455```js456const Fastify = require("fastify");457const { test }= require("node:test");458const myPlugin = require("../plugin/myFirstPlugin");459 460test("Test the Plugin Route", async t => {461 t.plan(5)462 const fastify = Fastify()463 464 fastify.register(myPlugin)465 466 fastify.get("/", async (request, reply) => {467 // Testing the fastify decorators468 t.assert.ifError(request.helloRequest)469 t.assert.ok(request.helloRequest, "Hello World")470 t.assert.ok(fastify.helloInstance, "Hello Fastify Instance")471 return ({ message: request.helloRequest })472 })473 474 const fastifyResponse = await fastify.inject({475 method: "GET",476 url: "/"477 })478 t.assert.strictEqual(fastifyResponse.statusCode, 200)479 t.assert.deepStrictEqual(JSON.parse(fastifyResponse.body), { message: "Hello World" })480})481```482 