1ステップで Spanner Emulator のレコード検証を行うnpmライブラリを作った
はじめに
最近Spannerを使用しているプロジェクトで開発していることが多いのですが、E2EテストにおいてDBのレコード検証を行うのが面倒だなと思うことがよくありました。特にUI操作 → DB側の検証という流れを自動化したいとき、もっと直観的にレコードの期待値を管理したいと思うことが多くありました。そこで、Playwrightのアサーションの1ステップのように、Spanner Emulatorのレコード検証を行うことができるライブラリを作りました。
概要
Playwrightのテストケース内で気軽に呼びたかったので、npmライブラリとして作成しています。以下コマンドでインストールできます。
npm install -D spanner-assert
使い方はテーブルレコードの期待値をJSONで定義し、それを実データと比較します。
例えば、以下のようなJSONを定義します。
{
"tables": {
"Users": {
"rows": [
{
"UserID": "user-001",
"Name": "Alice Example",
"Email": "alice@example.com",
"Status": 1,
"CreatedAt": "2024-01-01T00:00:00Z"
}
],
"count": 1
},
"Product": {
"rows": [
{
"ProductID": "product-001",
"Name": "Example Product",
"Price": 1999,
"IsActive": true,
"CategoryID": null,
"CreatedAt": "2024-01-01T00:00:00Z"
}
]
}
}
}
その後、Playwrightのテスト内でspannerAssertのクライアントを作成し、assertメソッドを呼び出します。そうすると、引数に与えたJSONの構造を解析して、期待値と実データを比較します。
import { createSpannerAssert } from 'spanner-assert'
import expectations from './expectations.json' with { type: 'json' }
const spannerAssert = createSpannerAssert({
connection: {
projectId: 'your-project-id',
instanceId: 'your-instance-id',
databaseId: 'your-database',
emulatorHost: '127.0.0.1:9010',
},
})
await spannerAssert.assert(expectations)
実際のレコードが期待値と異なる場合はjest-diffを用いた期待値と実値の比較が下記のように表示されます。
SpannerAssertionError: 1 expected row(s) not found in table "Users".
- Expected
+ Actual
Array [
Object {
- "Name": "Alice",
+ "Name": "Invalid Name",
},
]
例はPlaywrightでUIを操作した後にDBレコードを検証していますが、それ以外のテストライブラリでも使用可能です。
SSOTで期待値を管理することも可能
JSONを期待値として管理するので、以下のように、Playwrightのテストのステップとして期待値のJSONの値を使用することもできます。
await step('ユーザーを作成', async () => {
await page.goto('/users/new')
await page.fill("input[name='name']", expectations.tables.Users.rows[0].Name)
await page.fill(
"input[name='email']",
expectations.tables.Users.rows[0].Email, // DBレコードの期待値を使用
)
await page.click("button[type='submit']")
await spannerAssert.assert(expectations)
})
こうすることで期待値をSSOTのように使用することもでき、UIに入力する値と期待値を同じJSONで管理することができます。
複数DBにも対応
spannerAssertのインスタンスは複数作成できるので、複数のSpanner Emulatorのデータベースを同時に検証することもできます。
// DB1
const spannerAssert = createSpannerAssert({
connection: {
projectId: 'your-project-id',
instanceId: 'your-instance-id',
databaseId: 'your-database',
emulatorHost: '127.0.0.1:9010',
},
})
// DB2
const spannerAssert2 = createSpannerAssert({
connection: {
projectId: 'your-project-id',
instanceId: 'your-instance-id',
databaseId: 'your-database',
emulatorHost: '127.0.0.1:9010',
},
})
await spannerAssert.assert(expectations)
await spannerAssert2.assert(expectations2)
そのため、管理画面などの複数のDBを操作するプロダクトにおいても、インスタンス作成時の接続情報に接続したいDBの情報を指定することで、簡単に期待値を管理することができます。
現在は文字列、数値、真偽値、null、配列にのみ対応しています。
npmライブラリのPublishについて
npmライブラリを公開する場合、サプライチェーン攻撃に対しての対応が必須です。
そのため、今回作成するライブラリは以下のような対策を行いました。
- npmのOIDCを使用したリリース
- renovateによる依存パッケージの更新
- ghalintによるセキュリティリスクの検知
- husky+secretlintによるシークレットの誤commitの防止
- GitHub Actionsのconcurrency + timeoutの設定
- zizmorによるセキュリティリスクの検知
- publintによるパッケージのリリース
- Require two-factor authentication and disallow tokensの設定
ライブラリ開発時に便利だったこと
E2Eテスト
このライブラリをend-to-endでテストするには、DockerでSpannerのEmulatorを起動してseed投入などをする必要があったため、Taskfileを使用したE2Eテストを用意しました。早めにCIに組み込んでおくことで、ローカルでの開発時にもワンコマンドでE2Eテストを実行でき非常に便利でした。
まとめ
Playwrightの1ステップのようにSpannerのレコード検証を行うライブラリを作りました。
Discussion