测试
测试是应用程序开发中最重要的部分之一。Fastify 在测试方面非常灵活,并且与大多数测试框架兼容(例如 Tap,在下面的示例中使用)。
应用程序
让我们 cd
到一个名为“testing-example”的新目录中,并在终端中输入 npm init -y
。
运行 npm i fastify && npm i tap pino-pretty -D
关注点分离使测试变得容易
首先,我们将应用程序代码与服务器代码分离
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)
}
})
使用 fastify.inject() 的好处
Fastify 通过 light-my-request
内置支持伪造 HTTP 请求。
在引入任何测试之前,我们将使用 .inject
方法向我们的路由发出伪造请求
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()
首先,我们的代码将在异步函数内运行,使我们能够使用 async/await。
.inject
确保所有已注册的插件都已启动,并且我们的应用程序已准备好进行测试。最后,我们传递要使用的请求方法和路由。使用 await,我们可以无需回调即可存储响应。
在终端中运行测试文件 node app.test.js
status code: 200
body: {"hello":"world"}
使用 HTTP 请求注入进行测试
现在我们可以用实际的测试替换我们的 console.log
调用了!
在您的 package.json
中,将“test”脚本更改为
"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')
})
最后,在终端中运行 npm test
并查看您的测试结果!
inject
方法的功能远不止向 URL 发出简单的 GET 请求
fastify.inject({
method: String,
url: String,
query: Object,
payload: Object,
headers: Object,
cookies: Object
}, (error, response) => {
// your tests
})
通过省略回调函数,可以将 .inject
方法链接起来
fastify
.inject()
.get('/')
.headers({ foo: 'bar' })
.query({ foo: 'bar' })
.end((err, res) => { // the .end call will trigger the request
console.log(res.payload)
})
或者在 Promise 版本中
fastify
.inject({
method: String,
url: String,
query: Object,
payload: Object,
headers: Object,
cookies: Object
})
.then(response => {
// your tests
})
.catch(err => {
// handle error
})
也支持 Async/Await!
try {
const res = await fastify.inject({ method: String, url: String, payload: Object, headers: Object })
// your tests
} catch (err) {
// handle error
}
另一个示例:
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' })
})
})
使用正在运行的服务器进行测试
在使用 fastify.listen()
启动服务器后或在使用 fastify.ready()
初始化路由和插件后,也可以测试 Fastify。
示例:
使用上一个示例中的 app.js。
test-listen.js(使用 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)
})
或者,从 Node.js 18 开始,可以使用 fetch
,无需任何额外的依赖项
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(使用 SuperTest
进行测试)
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' })
})
如何检查 tap 测试
- 通过传递
{only: true}
选项来隔离您的测试
test('should ...', {only: true}, t => ...)
- 使用
npx
运行tap
> npx tap -O -T --node-arg=--inspect-brk test/<test-file.test.js>
-O
指定以启用only
选项的方式运行测试-T
指定不超时(调试时)--node-arg=--inspect-brk
将启动节点调试器
- 在 VS Code 中,创建并启动一个
Node.js: Attach
调试配置。无需进行任何修改。
现在,您应该能够在代码编辑器中逐步执行测试文件(以及 Fastify
的其余部分)。
插件
让我们 cd
到一个名为“testing-plugin-example”的新目录中,并在终端中输入 npm init -y
。
运行 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)
插件的基本示例。请参阅 插件指南
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)
})
详细了解 fastify.inject()
。在终端中运行测试文件 node test/myFirstPlugin.test.js
status code: 200
body: {"message":"Hello World"}
现在我们可以用实际的测试替换我们的 console.log
调用了!
在您的 package.json
中,将“test”脚本更改为
"test": "tap --reporter=list --watch"
创建端点的 tap 测试。
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" })
})
最后,在终端中运行 npm test
并查看您的测试结果!
测试 .decorate()
和 .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" })
})