💙

1ステップで Spanner Emulator のレコード検証を行うnpmライブラリを作った

に公開

はじめに

最近Spannerを使用しているプロジェクトで開発していることが多いのですが、E2EテストにおいてDBのレコード検証を行うのが面倒だなと思うことがよくありました。特にUI操作 → DB側の検証という流れを自動化したいとき、もっと直観的にレコードの期待値を管理したいと思うことが多くありました。そこで、Playwrightのアサーションの1ステップのように、Spanner Emulatorのレコード検証を行うことができるライブラリを作りました。

https://github.com/nu0ma/spanner-assert

概要

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テストを実行でき非常に便利でした。

https://github.com/nu0ma/spanner-assert/blob/9175ddfa6831278c22a887710054ba0bb1c0cd50/Taskfile.yml#L1-L103

まとめ

Playwrightの1ステップのようにSpannerのレコード検証を行うライブラリを作りました。

参考

Discussion