AdonisJSにテストを導入する
AdonisJSにテストを導入する
2022/11/08

こちらは、現時点で最新のAdonisJS v5の記事になります。

テストの導入手順とAdonis v4との変更点を紹介します。

公式ドキュメント
https://docs.adonisjs.com/guides/testing/introduction

テストはVowという内製?のテストツールからJapaに変更されています。
全体的に大きな変更はなく、メソッド名が違ったりの小さな変化でした。

Test機能を追加する

$ node ace configure tests

[ wait ]  installing @japa/runner, @japa/preset-adonis  ..

なぜかインストールが進まないので、一旦停止してpackageを追加します。

$ yarn add -D @japa/runner @japa/preset-adonis
$ node ace configure tests

TestDB設定

test用のDBを用意する。

テスト環境でのみ読み込まれる環境変数.env.testを作成します。

DB_DATABASE=adonis-test

テスト実行前にDBのマイグレーションを実行する設定を追加

  export const runnerHooks: Required<Pick<Config, 'setup' | 'teardown'>> = {
    setup: [
      () => TestUtils.ace().loadCommands(),
+     () => TestUtils.db().migrate(),
    ],
    teardown: [],
  }

Drive設定の追加

自分のサービスではAdonisで画像ファイルを作成する機能があるため、テストで作成されたファイルはテスト終了後に削除する設定が必要。

テストで作成されたファイルを削除する。

export const runnerHooks: Required<Pick<Config, 'setup' | 'teardown'>> = {
  teardown: [
    async () => {
      const list = await Drive.list('./').recursive().toArray()
      await Promise.all(await list.map((item) => Drive.delete(item.location)))
    }
  ],
}

このままだと全環境のファイルを削除してしまうので、テスト環境でのファイルの保存先を変更する。

export default driveConfig({
  disks: {
    local: {
			root: Env.get('NODE_ENV') === 'test' ? Application.tmpPath('upload') : Application.publicPath('upload'),
		}
	}
})

Testの書き方

テストを追加する

$ node ace make:test functional user
CREATE: tests/functional/user.spec.ts

作成されたファイルにテスト内容を書いていく。

テストの書き方はJapaなので公式サイトを参考に書いてください。
https://japa.dev/docs

Factoryを作成する

モデルのモックデータとなるFactoryを作成する

$ node ace make:factory user
CREATE: database/factories/UserFactory.ts
import user from 'App/Models/user'
import Factory from '@ioc:Adonis/Lucid/Factory'

export default Factory.define(user, ({ faker }) => {
  return {
    email: faker.internet.email(),
    password: faker.internet.password(),
  }
}).build()

Adonis v4との違い

テストツールをAdonis v4ではassertにchaiを使っていたが、Adonis v5からjapaというものに変わり、書き方が変わっているので変更点だけ紹介する。

全体的な書き方の違い

Adonis v4

const { test } = use('Test/Suite')('User')
const Factory = use('Factory')
const Mail = use('Mail')

const UserFactory = Factory.model('App/Models/User')

test('can create user if valid data', async ({ assert }) => {
  // some tests
})

Adonis v5

import { test } from '@japa/runner'
import Mail from '@ioc:Adonis/Addons/Mail'
import UserFactory from 'Database/factories/UserFactory'

// test関数の似た感じ
test.group('User', () => {
  test('can create user if valid data', async ({ assert, client }) => {
     // some tests
  })
})

Database Transaction

テスト間でデータが影響しないように、テスト毎にトランザクションを貼り、終了後にロールバックします。

Adonis v4

const { test, trait } = use('Test/Suite')('User registration')

trait('DatabaseTransactions')

Adonis v5

import Database from '@ioc:Adonis/Lucid/Database'

test.group('Group name', (group) => {
  group.each.setup(async () => {
    await Database.beginGlobalTransaction()
    return () => Database.rollbackGlobalTransaction()
  })
})

ApiClient

Adonis v4

const response = await client.post('/api/v1/user').send(data).end()

Adonis v5

const response = await client.post('/api/v1/user').fields(data)

Response Assert

ApiClientのレスポンスのアサーション

Adonis v4

response.assertJSONSubset([
  {
    message: '会員登録完了しました',
  },
])

Adonis v5

response.assertBodyContains({
  message: '会員登録完了しました',
})

// error response
response.assertBodyContains({
  errors: [{
    message: 'メールアドレスが正しくありません',
    field: 'email',
    rule: 'email',
  }]
})

メール

Adonis v4

test('hoge', async ({}) => {
  Mail.fake()
  const recentEmail = Mail.pullRecent()
  assert.equal(recentEmail.message.to[0].address, email)
  assert.equal(recentEmail.message.subject, '会員登録いただきありがとうございます。')

  Mail.restore()
})

Adonis v5

test('hoge', async ({}) => {
  const mailer = Mail.fake()
  mailer.exists((mail) => {
    const sameAddress = mail.to?.some(to => to.address == email) || false
    const sameSubject = mail.subject === '会員登録いただきありがとうございます。'
    return sameAddress && sameSubject
  })
  Mail.restore()
})

Factory

Adonis v4

const user = await UserFactory.create({
  password: 'password',
})

Adonis v5

const user = await UserFactory.merge({
  password: 'password',
}).create()

この他にも、AdonisJS v5へのアップグレードで行ったことをまとめました。
なにか役に立てたら幸いです。