Repository: fastify/fastify (JavaScript)

Issue:
Consider using `.after()` inside `.addHook` and `.use` calls
Here's a summary of the bug from fastify/fastify-cookie#5:

```js
fastify.register(require('fastify-cookie'))

fastify.addHook('preHandler', (request, reply, next) => {
  console.log(request.cookies) // {} - Expected cookies to have been parsed here :(
  next()
})

fastify.get('/', (request, reply) => {
  console.log(request.cookies) // {foo: 'bar'} - Now the cookies have been parsed
  reply.send('hello')
})
```

Since plugins are registered asynchronously, the user's call to `fastify.addHook` happens before `fastify-cookie` calls `.addHook`, so even though it looks like the user added their hook after `fastify-cookie`, it actually gets added before the cookie hook.

This could be fixed by using `this.after()` inside `.addHook` so that the hook would get added after the plugin's hooks. We already do this for `fastify.route` (which includes the route shorthands) as well as `fastify.setNotFoundHandler`.

Note that this change would affect code like this:

```js
fastify.addHook('preHandler', function preHandlerA () {})

fastify.register((instance, options, next) => {
  instance.get('/user', function handler () {})
  // Currently: Both preHandlerA and preHandlerB run before the '/user' handler
  // With .after(): Only preHandlerA runs before the '/user' handler
})

fastify.addHook('preHandler', function preHandlerB () {})
```

I think that using `.after()` inside `.addHook` and `.use` calls would be a good idea because it would make the ordering of hooks and middleware more predictable. Does everyone agree?


Discussion:
I think this is a good idea. @delvedor what do you think?
Makes sense to me.
It is ok for me.

**NB:** In the "hook pattern", each listener has to be independent from each other.
I've been trying to implement this and now I have a question.

Since this change means that handlers—and not-found handlers—will only run hooks/middleware that are defined before the handler, what do we do about the default not-found handler? Currently it gets set when the `fastify` instance is created, which means that it won't run any hooks or middleware. Is that okay? Or should we make it a special case and allow all non-encapsulated hooks and middleware to run before it?

The decision on this would solve this current issue:

```js
const fastify = require('fastify')()

fastify.register(require('a-plugin-with-a-preHandler'))

fastify.addHook('preHandler', function preHandlerA (request, reply, next) {
  request.preHandlerA = true
  next()
})

fastify.listen()
```

The default not-found handler will have `preHandlerA` run before it, but not the `preHandler` from the plugin.
> Since this change means that handlers—and not-found handlers—will only run hooks/middleware that are defined before the handler, what do we do about the default not-found handler? Currently it gets set when the fastify instance is created, which means that it won't run any hooks or middleware. Is that okay? Or should we make it a special case and allow all non-encapsulated hooks and middleware to run before it?

I don't think this should be the correct plan, ideally all hooks and middlewares defined "at the same level" should all be executed. I think we might want to keep things the way we are for 1.0.0, and rethink the encapsulation model a bit for 2.0.0.
In that case, we'll need to document the `fastify.after()` method.
Although a problem currently is that the levels aren't consistent. For example:

```js
const fp = require('fastify-plugin')
const fastify = require('fastify')()

fastify.decorateRequest('payload', '')

fastify.register(fp(async function (instance) {
  instance.addHook('preHandler', async function A (request) { // third
    request.payload += 'A'
  })
}))

fastify.addHook('preHandler', async function B (request) { // first
  request.payload += 'B'
})

fastify.get('/', function handler (request, reply) {
  reply.send(request.payload)
})

fastify.register(fp(async function (instance) {
  instance.addHook('preHandler', async function C (request) { // never runs
    request.payload += 'C'
  })
}))

fastify.addHook('preHandler', async function D (request) { // second
  request.payload += 'D'
})

fastify.inject('/', (err, res) => {
  console.log(res.payload) // 'BDA'
})
```

We'll need to explain in the documentation to always register plugins before routes that need them and to use `.after()` for hooks/middleware that need to come after plugins.
I agree that we should update the docs and make a recommendation on how register the components of the application.

```
plugins (cookies, compression...)
decorators
routes (could be also a plugin)
```

I'll work on this.
Regarding the internal usage of `after` to register hooks and middlewares I'm 👍 .

ps: the `BDA` order is correct, because we `push` the new hooks, ands register are executed after the current sync context.
The second register (`C`) will not work because an `after` function runs *always* after previous one declared `register`.
@delvedor If we internally use `after` when registering hooks and middleware then the result of my example will be `AB`. I think this would be a better developer experience because linear ordering is easier to work with (it's also similar to how Express and Koa register middleware), but I'm wondering if you agree?
I think we might want to improve a bit the situation. Instead of generating an hook and middleware runner for every route, we should generate them for every plugin, after the plugin has finished loading. This should solve most of the issues. I remember we implemented them in this way in the beginning, but I do not remember why we changed them. 
@mcollina to do what you suggest we should figure out a way to understand when a plugin has finished to load (can we do that outside avvio?) and then register hooks - middlewares - routes (in this order).
Basically we should popolate the context object and register the routes only at the end of the plugin work (how will this work top level?).

We add hook and middlewares for every route because we must store them inside the [store](https://github.com/delvedor/find-my-way#onmethod-path-handler-store) object of `find-my-way`.
> Basically we should popolate the context object and register the routes only at the end of the plugin work (how will this work top level?).

No. I mean that the route context links to a plugin context, which includes all those details which will be shared. 
@mcollina That makes sense (generating hooks/middleware for each Fastify instance instead of each route). It was probably changed because of non-encapsulated plugins:

```js
fastify.register(require('plugin-1'))

fastify.get('/1', ...) // has only plugin-1

fastify.register(require('plugin-2'))

fastify.get('/2', ...) // has plugin-1 and plugin-2
```

They're all at the same level, but `plugin-2` gets registered after the `get('/1')` route is already set up.
@mcollina A solution would be to have hooks/middlware use `after` and then have routes use `ready`. That way, routes will be set up once all of the plugins/hooks/middleware for the Fastify instance are ready. However, this is only possible if `ready` doesn't automatically start the boot process.

Also for future reference, this solution would cause the result of my [earlier example](https://github.com/fastify/fastify/issues/748#issuecomment-362954220) to be `ABCD`.
Let’s just document what we have and ship 1.0.0. Then we can focus on this issue
This was regressed in https://github.com/fastify/fastify/pull/474. This is caused by the hookRunner (and before that fast-iterator) creating a closure inside `.after` from within `.route`. Note that this happens only when plugins are involved, otherwise after takes care of them.

This is a test that reproduces the problem:

```
diff --git a/test/hooks.test.js b/test/hooks.test.js
index c0f00e6..6db8f38 100644
--- a/test/hooks.test.js
+++ b/test/hooks.test.js
@@ -6,6 +6,7 @@ const sget = require('simple-get').concat
 const stream = require('stream')
 const Fastify = require('..')
 const fs = require('fs')
+const fp = require('fastify-plugin')

 const payload = { hello: 'world' }

@@ -1252,3 +1253,44 @@ if (Number(process.versions.node[0]) >= 8) {
   t.pass('Skip because Node version < 8')
   t.end()
 }
+
+test('Register an hook after a plugin', t => {
+  t.plan(6)
+  const fastify = Fastify()
+
+  fastify.register(fp(function (instance, opts, next) {
+    instance.addHook('preHandler', function (req, reply, next) {
+      t.ok('called')
+      next()
+    })
+
+    instance.get('/', function (request, reply) {
+      reply.send({ hello: 'world' })
+    })
+
+    next()
+  }))
+
+  fastify.register(fp(function (instance, opts, next) {
+    fastify.addHook('preHandler', function (req, reply, next) {
+      t.ok('called')
+      next()
+    })
+
+    fastify.addHook('preHandler', function (req, reply, next) {
+      t.ok('called')
+      next()
+    })
+
+    next()
+  }))
+
+  fastify.inject({
+    url: '/',
+    method: 'GET'
+  }, (err, res) => {
+    t.error(err)
+    t.is(res.statusCode, 200)
+    t.deepEqual(JSON.parse(res.payload), { hello: 'world' })
+  })
+})
```
The problem is generated here:
https://github.com/fastify/fastify/blob/efc4525236b1d2831521fd3735001d110efc83dd/fastify.js#L490

Since we use concat we loose the reference to the instance hooks and we don't get any further updates. If you run the same test with `onRequest` everything will work as expected.
I'll work on a solution.
Closed by #762.
With the fix from #762 the result of my [example](#issuecomment-362954220) will be `BDAC`. @delvedor are you no longer interested in using `after` to make the result `ABCD`?
Currently I have no time for that, but if you can, fire a pr! :)



Patch:
diff --git a/fastify.js b/fastify.js
index 55a81744a27..563b3a90e86 100644
--- a/fastify.js
+++ b/fastify.js
@@ -26,6 +26,8 @@ const runHooks = require('./lib/hookRunner')
 
 const DEFAULT_JSON_BODY_LIMIT = 1024 * 1024 // 1 MiB
 
+const childrenKey = Symbol('fastify.children')
+
 function validateBodyLimitOption (jsonBodyLimit) {
   if (jsonBodyLimit === undefined) return
   if (!Number.isInteger(jsonBodyLimit) || jsonBodyLimit <= 0) {
@@ -55,7 +57,9 @@ function build (options) {
     log = loggerUtils.createLogger(options.logger)
   }
 
-  const fastify = {}
+  const fastify = {
+    [childrenKey]: []
+  }
   const router = FindMyWay({
     defaultRoute: defaultRoute,
     ignoreTrailingSlash: options.ignoreTrailingSlash,
@@ -343,6 +347,8 @@ function build (options) {
     }
 
     const instance = Object.create(old)
+    old[childrenKey].push(instance)
+    instance[childrenKey] = []
     instance._Reply = Reply.buildReply(instance._Reply)
     instance._Request = Request.buildRequest(instance._Request)
     instance._contentTypeParser = ContentTypeParser.buildContentTypeParser(instance._contentTypeParser)
@@ -583,8 +589,15 @@ function build (options) {
       const prefix = this._routePrefix
       url = prefix + (url === '/' && prefix.length > 0 ? '' : url)
     }
-    this._middlewares.push([url, fn])
-    return this
+    return this.after((err, done) => {
+      addMiddleware(this, [url, fn])
+      done(err)
+    })
+  }
+
+  function addMiddleware (instance, middleware) {
+    instance._middlewares.push(middleware)
+    instance[childrenKey].forEach(child => addMiddleware(child, middleware))
   }
 
   function addHook (name, fn) {
@@ -597,11 +610,19 @@ function build (options) {
       this._hooks.validate(name, fn)
       onRouteHooks.push(fn)
     } else {
-      this._hooks.add(name, fn.bind(this))
+      this.after((err, done) => {
+        _addHook(this, name, fn)
+        done(err)
+      })
     }
     return this
   }
 
+  function _addHook (instance, name, fn) {
+    instance._hooks.add(name, fn.bind(instance))
+    instance[childrenKey].forEach(child => _addHook(child, name, fn))
+  }
+
   function addSchema (name, schema) {
     throwIfAlreadyBound('Cannot call "addSchema" when fastify instance is already started!')
     this._schemas.add(name, schema)


Test patch:
diff --git a/test/hooks.test.js b/test/hooks.test.js
index 3eba6af1a65..43641b35537 100644
--- a/test/hooks.test.js
+++ b/test/hooks.test.js
@@ -103,36 +103,54 @@ test('hooks', t => {
 })
 
 test('onRequest hook should support encapsulation / 1', t => {
-  t.plan(3)
+  t.plan(5)
   const fastify = Fastify()
 
   fastify.register((instance, opts, next) => {
-    instance.addHook('onRequest', () => {})
-    t.is(instance._hooks.onRequest.length, 1)
+    instance.addHook('onRequest', (req, res, next) => {
+      t.strictEqual(req.url, '/plugin')
+      next()
+    })
+
+    instance.get('/plugin', (request, reply) => {
+      reply.send()
+    })
+
     next()
   })
 
-  fastify.ready(err => {
+  fastify.get('/root', (request, reply) => {
+    reply.send()
+  })
+
+  fastify.inject('/root', (err, res) => {
+    t.error(err)
+    t.strictEqual(res.statusCode, 200)
+  })
+
+  fastify.inject('/plugin', (err, res) => {
     t.error(err)
-    t.is(fastify._hooks.onRequest.length, 0)
+    t.strictEqual(res.statusCode, 200)
   })
 })
 
 test('onRequest hook should support encapsulation / 2', t => {
   t.plan(3)
   const fastify = Fastify()
+  var pluginInstance
 
   fastify.addHook('onRequest', () => {})
 
   fastify.register((instance, opts, next) => {
     instance.addHook('onRequest', () => {})
-    t.is(instance._hooks.onRequest.length, 2)
+    pluginInstance = instance
     next()
   })
 
   fastify.ready(err => {
     t.error(err)
     t.is(fastify._hooks.onRequest.length, 1)
+    t.is(pluginInstance._hooks.onRequest.length, 2)
   })
 })
 
@@ -484,36 +502,55 @@ test('onRoute hook should preserve system route configuration', t => {
 })
 
 test('onResponse hook should support encapsulation / 1', t => {
-  t.plan(3)
+  t.plan(5)
   const fastify = Fastify()
 
   fastify.register((instance, opts, next) => {
-    instance.addHook('onResponse', () => {})
-    t.is(instance._hooks.onResponse.length, 1)
+    instance.addHook('onResponse', (res, next) => {
+      t.strictEqual(res.plugin, true)
+      next()
+    })
+
+    instance.get('/plugin', (request, reply) => {
+      reply.res.plugin = true
+      reply.send()
+    })
+
     next()
   })
 
-  fastify.ready(err => {
+  fastify.get('/root', (request, reply) => {
+    reply.send()
+  })
+
+  fastify.inject('/root', (err, res) => {
+    t.error(err)
+    t.strictEqual(res.statusCode, 200)
+  })
+
+  fastify.inject('/plugin', (err, res) => {
     t.error(err)
-    t.is(fastify._hooks.onResponse.length, 0)
+    t.strictEqual(res.statusCode, 200)
   })
 })
 
 test('onResponse hook should support encapsulation / 2', t => {
   t.plan(3)
   const fastify = Fastify()
+  var pluginInstance
 
   fastify.addHook('onResponse', () => {})
 
   fastify.register((instance, opts, next) => {
     instance.addHook('onResponse', () => {})
-    t.is(instance._hooks.onResponse.length, 2)
+    pluginInstance = instance
     next()
   })
 
   fastify.ready(err => {
     t.error(err)
     t.is(fastify._hooks.onResponse.length, 1)
+    t.is(pluginInstance._hooks.onResponse.length, 2)
   })
 })
 
@@ -577,18 +614,20 @@ test('onResponse hook should support encapsulation / 3', t => {
 test('onSend hook should support encapsulation / 1', t => {
   t.plan(3)
   const fastify = Fastify()
+  var pluginInstance
 
   fastify.addHook('onSend', () => {})
 
   fastify.register((instance, opts, next) => {
     instance.addHook('onSend', () => {})
-    t.is(instance._hooks.onSend.length, 2)
+    pluginInstance = instance
     next()
   })
 
   fastify.ready(err => {
     t.error(err)
     t.is(fastify._hooks.onSend.length, 1)
+    t.is(pluginInstance._hooks.onSend.length, 2)
   })
 })
 
@@ -1334,6 +1373,285 @@ test('Register an hook after a plugin inside a plugin (with beforeHandler)', t =
   })
 })
 
+test('Register hooks inside a plugin after an encapsulated plugin', t => {
+  t.plan(7)
+  const fastify = Fastify()
+
+  fastify.register(function (instance, opts, next) {
+    instance.get('/', function (request, reply) {
+      reply.send({ hello: 'world' })
+    })
+
+    next()
+  })
+
+  fastify.register(fp(function (instance, opts, next) {
+    instance.addHook('onRequest', function (req, res, next) {
+      t.ok('called')
+      next()
+    })
+
+    instance.addHook('preHandler', function (request, reply, next) {
+      t.ok('called')
+      next()
+    })
+
+    instance.addHook('onSend', function (request, reply, payload, next) {
+      t.ok('called')
+      next()
+    })
+
+    instance.addHook('onResponse', function (res, next) {
+      t.ok('called')
+      next()
+    })
+
+    next()
+  }))
+
+  fastify.inject('/', (err, res) => {
+    t.error(err)
+    t.is(res.statusCode, 200)
+    t.deepEqual(JSON.parse(res.payload), { hello: 'world' })
+  })
+})
+
+test('onRequest hooks should run in the order in which they are defined', t => {
+  t.plan(9)
+  const fastify = Fastify()
+
+  fastify.register(function (instance, opts, next) {
+    instance.addHook('onRequest', function (req, res, next) {
+      t.strictEqual(req.previous, undefined)
+      req.previous = 1
+      next()
+    })
+
+    instance.get('/', function (request, reply) {
+      t.strictEqual(request.req.previous, 5)
+      reply.send({ hello: 'world' })
+    })
+
+    instance.register(fp(function (i, opts, next) {
+      i.addHook('onRequest', function (req, res, next) {
+        t.strictEqual(req.previous, 1)
+        req.previous = 2
+        next()
+      })
+      next()
+    }))
+
+    next()
+  })
+
+  fastify.register(fp(function (instance, opts, next) {
+    instance.addHook('onRequest', function (req, res, next) {
+      t.strictEqual(req.previous, 2)
+      req.previous = 3
+      next()
+    })
+
+    instance.register(fp(function (i, opts, next) {
+      i.addHook('onRequest', function (req, res, next) {
+        t.strictEqual(req.previous, 3)
+        req.previous = 4
+        next()
+      })
+      next()
+    }))
+
+    instance.addHook('onRequest', function (req, res, next) {
+      t.strictEqual(req.previous, 4)
+      req.previous = 5
+      next()
+    })
+
+    next()
+  }))
+
+  fastify.inject('/', (err, res) => {
+    t.error(err)
+    t.strictEqual(res.statusCode, 200)
+    t.deepEqual(JSON.parse(res.payload), { hello: 'world' })
+  })
+})
+
+test('preHandler hooks should run in the order in which they are defined', t => {
+  t.plan(9)
+  const fastify = Fastify()
+
+  fastify.register(function (instance, opts, next) {
+    instance.addHook('preHandler', function (request, reply, next) {
+      t.strictEqual(request.previous, undefined)
+      request.previous = 1
+      next()
+    })
+
+    instance.get('/', function (request, reply) {
+      t.strictEqual(request.previous, 5)
+      reply.send({ hello: 'world' })
+    })
+
+    instance.register(fp(function (i, opts, next) {
+      i.addHook('preHandler', function (request, reply, next) {
+        t.strictEqual(request.previous, 1)
+        request.previous = 2
+        next()
+      })
+      next()
+    }))
+
+    next()
+  })
+
+  fastify.register(fp(function (instance, opts, next) {
+    instance.addHook('preHandler', function (request, reply, next) {
+      t.strictEqual(request.previous, 2)
+      request.previous = 3
+      next()
+    })
+
+    instance.register(fp(function (i, opts, next) {
+      i.addHook('preHandler', function (request, reply, next) {
+        t.strictEqual(request.previous, 3)
+        request.previous = 4
+        next()
+      })
+      next()
+    }))
+
+    instance.addHook('preHandler', function (request, reply, next) {
+      t.strictEqual(request.previous, 4)
+      request.previous = 5
+      next()
+    })
+
+    next()
+  }))
+
+  fastify.inject('/', (err, res) => {
+    t.error(err)
+    t.strictEqual(res.statusCode, 200)
+    t.deepEqual(JSON.parse(res.payload), { hello: 'world' })
+  })
+})
+
+test('onSend hooks should run in the order in which they are defined', t => {
+  t.plan(8)
+  const fastify = Fastify()
+
+  fastify.register(function (instance, opts, next) {
+    instance.addHook('onSend', function (request, reply, payload, next) {
+      t.strictEqual(request.previous, undefined)
+      request.previous = 1
+      next()
+    })
+
+    instance.get('/', function (request, reply) {
+      reply.send({})
+    })
+
+    instance.register(fp(function (i, opts, next) {
+      i.addHook('onSend', function (request, reply, payload, next) {
+        t.strictEqual(request.previous, 1)
+        request.previous = 2
+        next()
+      })
+      next()
+    }))
+
+    next()
+  })
+
+  fastify.register(fp(function (instance, opts, next) {
+    instance.addHook('onSend', function (request, reply, payload, next) {
+      t.strictEqual(request.previous, 2)
+      request.previous = 3
+      next()
+    })
+
+    instance.register(fp(function (i, opts, next) {
+      i.addHook('onSend', function (request, reply, payload, next) {
+        t.strictEqual(request.previous, 3)
+        request.previous = 4
+        next()
+      })
+      next()
+    }))
+
+    instance.addHook('onSend', function (request, reply, payload, next) {
+      t.strictEqual(request.previous, 4)
+      next(null, '5')
+    })
+
+    next()
+  }))
+
+  fastify.inject('/', (err, res) => {
+    t.error(err)
+    t.strictEqual(res.statusCode, 200)
+    t.deepEqual(JSON.parse(res.payload), 5)
+  })
+})
+
+test('onResponse hooks should run in the order in which they are defined', t => {
+  t.plan(8)
+  const fastify = Fastify()
+
+  fastify.register(function (instance, opts, next) {
+    instance.addHook('onResponse', function (res, next) {
+      t.strictEqual(res.previous, undefined)
+      res.previous = 1
+      next()
+    })
+
+    instance.get('/', function (request, reply) {
+      reply.send({ hello: 'world' })
+    })
+
+    instance.register(fp(function (i, opts, next) {
+      i.addHook('onResponse', function (res, next) {
+        t.strictEqual(res.previous, 1)
+        res.previous = 2
+        next()
+      })
+      next()
+    }))
+
+    next()
+  })
+
+  fastify.register(fp(function (instance, opts, next) {
+    instance.addHook('onResponse', function (res, next) {
+      t.strictEqual(res.previous, 2)
+      res.previous = 3
+      next()
+    })
+
+    instance.register(fp(function (i, opts, next) {
+      i.addHook('onResponse', function (res, next) {
+        t.strictEqual(res.previous, 3)
+        res.previous = 4
+        next()
+      })
+      next()
+    }))
+
+    instance.addHook('onResponse', function (res, next) {
+      t.strictEqual(res.previous, 4)
+      next()
+    })
+
+    next()
+  }))
+
+  fastify.inject('/', (err, res) => {
+    t.error(err)
+    t.strictEqual(res.statusCode, 200)
+    t.deepEqual(JSON.parse(res.payload), { hello: 'world' })
+  })
+})
+
 if (Number(process.versions.node[0]) >= 8) {
   require('./hooks-async')(t)
 } else {
diff --git a/test/middleware.test.js b/test/middleware.test.js
index 6f4a9e2dfd5..b3e1ea75dbb 100644
--- a/test/middleware.test.js
+++ b/test/middleware.test.js
@@ -152,7 +152,7 @@ test('use helmet and cors', t => {
 })
 
 test('middlewares should support encapsulation / 1', t => {
-  t.plan(9)
+  t.plan(8)
 
   const instance = fastify()
 
@@ -162,7 +162,6 @@ test('middlewares should support encapsulation / 1', t => {
       t.fail('this should not be called')
       next()
     })
-    t.ok(i._middlewares.length > 0)
     done()
   })
 
@@ -778,3 +777,96 @@ test('middlewares should be able to respond with a stream', t => {
     t.is(res.statusCode, 200)
   })
 })
+
+test('Use a middleware inside a plugin after an encapsulated plugin', t => {
+  t.plan(5)
+  const f = fastify()
+
+  f.register(function (instance, opts, next) {
+    instance.use(function (req, res, next) {
+      t.ok('first middleware called')
+      next()
+    })
+
+    instance.get('/', function (request, reply) {
+      reply.send({ hello: 'world' })
+    })
+
+    next()
+  })
+
+  f.register(fp(function (instance, opts, next) {
+    instance.use(function (req, res, next) {
+      t.ok('second middleware called')
+      next()
+    })
+
+    next()
+  }))
+
+  f.inject('/', (err, res) => {
+    t.error(err)
+    t.is(res.statusCode, 200)
+    t.deepEqual(JSON.parse(res.payload), { hello: 'world' })
+  })
+})
+
+test('middlewares should run in the order in which they are defined', t => {
+  t.plan(9)
+  const f = fastify()
+
+  f.register(function (instance, opts, next) {
+    instance.use(function (req, res, next) {
+      t.strictEqual(req.previous, undefined)
+      req.previous = 1
+      next()
+    })
+
+    instance.get('/', function (request, reply) {
+      t.strictEqual(request.req.previous, 5)
+      reply.send({ hello: 'world' })
+    })
+
+    instance.register(fp(function (i, opts, next) {
+      i.use(function (req, res, next) {
+        t.strictEqual(req.previous, 1)
+        req.previous = 2
+        next()
+      })
+      next()
+    }))
+
+    next()
+  })
+
+  f.register(fp(function (instance, opts, next) {
+    instance.use(function (req, res, next) {
+      t.strictEqual(req.previous, 2)
+      req.previous = 3
+      next()
+    })
+
+    instance.register(fp(function (i, opts, next) {
+      i.use(function (req, res, next) {
+        t.strictEqual(req.previous, 3)
+        req.previous = 4
+        next()
+      })
+      next()
+    }))
+
+    instance.use(function (req, res, next) {
+      t.strictEqual(req.previous, 4)
+      req.previous = 5
+      next()
+    })
+
+    next()
+  }))
+
+  f.inject('/', (err, res) => {
+    t.error(err)
+    t.strictEqual(res.statusCode, 200)
+    t.deepEqual(JSON.parse(res.payload), { hello: 'world' })
+  })
+})