UNPKG

fastify

Version:

Fast and low overhead web framework, for Node.js

480 lines (356 loc) 10.7 kB
<h1 style="text-align: center;">Fastify</h1> # Testing <a id="testing"></a> Testing is one of the most important parts of developing an application. Fastify is very flexible when it comes to testing and is compatible with most testing frameworks (such as [Tap](https://www.npmjs.com/package/tap), which is used in the 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**: ```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**: ```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 to [`light-my-request`](https://github.com/fastify/light-my-request). Before introducing any tests, we will use the `.inject` method to make a fake request to our route: **app.test.js**: ```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 to async/await. `.inject` ensures all registered plugins have booted up and our application is ready 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` ```sh 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**: ```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: ```js 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: ```js 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 ```js 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! ```js try { const res = await fastify.inject({ method: String, url: String, payload: Object, headers: Object }) // your tests } catch (err) { // handle error } ``` #### Another Example: **app.js** ```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** ```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()` or after initializing routes and plugins with `fastify.ready()`. #### Example: Uses **app.js** from the previous example. **test-listen.js** (testing with [`undici`](https://www.npmjs.com/package/undici)) ```js 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, [`fetch`](https://nodejs.org/docs/latest-v18.x/api/globals.html#fetch) may be used without requiring any extra dependencies: **test-listen.js** ```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 with [`SuperTest`](https://www.npmjs.com/package/supertest)) ```js 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 ```javascript test('should ...', {only: true}, t => ...) ``` 2. Run `tap` using `npx` ```bash > 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 3. In VS Code, create and launch a `Node.js: Attach` debug configuration. No modification should be necessary. Now you should be able to step through your test file (and the rest of `Fastify`) 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**: ```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](./Plugins-Guide.md) **test/myFirstPlugin.test.js**: ```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()```](#benefits-of-using-fastifyinject). Run the test file in your terminal `node test/myFirstPlugin.test.js` ```sh 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**: ```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**: ```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" }) }) ```