メインコンテンツにスキップ
バージョン: 最新 (v5.0.x)

テスト

テストは、アプリケーション開発において最も重要な部分の1つです。 Fastifyはテストに関して非常に柔軟性があり、ほとんどのテストフレームワーク(以下の例で使用されているTapなど)と互換性があります。

アプリケーション

ターミナルで「testing-example」という新しいディレクトリにcdし、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は、fastify.listen()でサーバーを起動した後、またはfastify.ready()でルートとプラグインを初期化した後にもテストできます。

例:

前の例の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(
'https://#:' + 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(
'https://#:' + 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テストを検査する方法

  1. {only: true}オプションを渡してテストを分離します
test('should ...', {only: true}, t => ...)
  1. npxを使用してtapを実行します
> npx tap -O -T --node-arg=--inspect-brk test/<test-file.test.js>
  • -Oは、onlyオプションを有効にしてテストを実行することを指定します
  • -Tは、タイムアウトしないことを指定します(デバッグ中)
  • --node-arg=--inspect-brkは、ノードデバッガーを起動します
  1. VS Codeで、Node.js: Attachデバッグ構成を作成して起動します。変更は不要です。

これで、コードエディターでテストファイル(および残りのFastify)をステップ実行できるようになります。

プラグイン

ターミナルで「testing-plugin-example」という新しいディレクトリにcdし、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" })
})