<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 [Node Test Runner](https://nodejs.org/api/test.html),
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 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": "node --test --watch"`

**app.test.js**:

```js
'use strict'

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

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

  const response = await app.inject({
    method: 'GET',
    url: '/'
  })
  t.assert.strictEqual(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 { test } = require('node:test')
const buildFastify = require('./app')

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.after(() => fastify.close())

  fastify.inject({
    method: 'GET',
    url: '/'
  }, (err, response) => {
    t.assert.ifError(err)
    t.assert.strictEqual(response.statusCode, 200)
    t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8')
    t.assert.deepStrictEqual(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 { test } = require('node:test')
const { Client } = require('undici')
const buildFastify = require('./app')

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.after(() => {
    fastify.close()
    client.close()
  })

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

  t.assert.strictEqual(await response.body.text(), '{"hello":"world"}')
  t.assert.strictEqual(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 { test } = require('node:test')
const buildFastify = require('./app')

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

  const fastify = buildFastify()

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

  await fastify.listen()

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

  t.assert.strictEqual(response.status, 200)
  t.assert.strictEqual(
    response.headers.get('content-type'),
    'application/json; charset=utf-8'
  )
  const jsonResult = await response.json()
  t.assert.strictEqual(jsonResult.hello, 'world')
})
```

**test-ready.js** (testing with
[`SuperTest`](https://www.npmjs.com/package/supertest))
```js
const { test } = require('node:test')
const supertest = require('supertest')
const buildFastify = require('./app')

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

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

  await fastify.ready()

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

### How to inspect node tests
1. Isolate your test by passing the `{only: true}` option
```javascript
test('should ...', {only: true}, t => ...)
```
2. Run `node --test`
```bash
> node --test --test-only --node-arg=--inspect-brk test/<test-file.test.js>
```
- `--test-only` specifies to run tests with the `only` option enabled
- `--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`

**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 { test } = require("node:test");
const myPlugin = require("../plugin/myFirstPlugin");

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": "node --test --watch"`

Create the test for the endpoint.

**test/myFirstPlugin.test.js**:

```js
const Fastify = require("fastify");
const { test } = require("node:test");
const myPlugin = require("../plugin/myFirstPlugin");

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.assert.strictEqual(fastifyResponse.statusCode, 200)
    t.assert.deepStrictEqual(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 { test }= require("node:test");
const myPlugin = require("../plugin/myFirstPlugin");

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.assert.ifError(request.helloRequest)
        t.assert.ok(request.helloRequest, "Hello World")
        t.assert.ok(fastify.helloInstance, "Hello Fastify Instance")
        return ({ message: request.helloRequest })
    })

    const fastifyResponse = await fastify.inject({
        method: "GET",
        url: "/"
    })
    t.assert.strictEqual(fastifyResponse.statusCode, 200)
    t.assert.deepStrictEqual(JSON.parse(fastifyResponse.body), { message: "Hello World" })
})
```
