mcpField
S
serverpod-testing

serverpod-testing

github.com/serverpodテスト・品質3.3k45 回インストール1 日前に更新
リポジトリ

serverpod-testing について

serverpod-testing は、エンドポイントやビジネスロジックを検証する Dart 開発者向けの Serverpod テストガイドです。サーバーコンテキスト付きテスト、データベースのロールバック、認証、ストリームのテストを説明します。

主な機能

  1. 01生成されたテストツールでエンドポイントを呼び出す
  2. 02セッションと認証を設定
  3. 03テスト用データベースのロールバックを制御
  4. 04ストリームと Session を使うロジックをテスト

活用シーン

  1. 01エンドポイントの統合動作を検証
  2. 02データベースセッションが必要なロジックをテスト
  3. 03テストデータを分離してストリームイベントを確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/serverpod/serverpod --skill serverpod-testing

インストール後、Claude Code で/serverpod-testingを入力して読み込まれたか確認します。

同梱ファイルreferences/with-serverpod-options.md

詳細

Serverpod テスト

生成されたテストツールを使うと、サーバーの完全なコンテキスト(DB、キャッシュなど)を使って、テスト内でエンドポイントを呼び出せます。serverpod_test を直接インポートするのではなく、生成されたテストツールのファイルをインポートしてください。このファイルは必要なものをすべて再エクスポートしています。インポートパスは config/generator.yaml の server_test_tools_path で指定します。以下の例では、一般的な出力先である test_tools/serverpod_test_tools.dart を使用しています。

Given/when/then の記述を優先してください。ネストしたグループとテスト名を通して、コードを読まなくても失敗の原因を説明できるよう、明確な Given、when、then がそれぞれ 1 つずつ含まれるようにしてください。

基本的なテスト

import 'package:test/test.dart';
import 'test_tools/serverpod_test_tools.dart';

void main() {
  withServerpod('Given Greeting endpoint', (sessionBuilder, endpoints) {
    test('when calling hello then returns greeting', () async {
      final greeting = await endpoints.greeting.hello(sessionBuilder, 'Bob');
      expect(greeting.message, 'Hello Bob');
    });
  });
}

セッションビルダー

sessionBuilder.copyWith(...) を使って、変更したセッションを作成します。DB 操作やヘルパーへの受け渡しに使う Session を取得するには、sessionBuilder.build() を呼び出してください。

認証済みテスト
withServerpod('Given AuthEndpoint', (sessionBuilder, endpoints) {
  final userId = '550e8400-e29b-41d4-a716-446655440000';

  group('when authenticated', () {
    var authed = sessionBuilder.copyWith(
      authentication: AuthenticationOverride.authenticationInfo(userId, {Scope('user')}),
    );

    test('then hello succeeds', () async {
      final greeting = await endpoints.authExample.hello(authed, 'Michael');
      expect(greeting, 'Hello, Michael!');
    });
  });

  group('when unauthenticated', () {
    var unauthed = sessionBuilder.copyWith(
      authentication: AuthenticationOverride.unauthenticated(),
    );

    test('then hello throws', () async {
      await expectLater(
        endpoints.authExample.hello(unauthed, 'Michael'),
        throwsA(isA<ServerpodUnauthenticatedException>()),
      );
    });
  });
});
データベースへのデータ投入
withServerpod('Given Products endpoint', (sessionBuilder, endpoints) {
  var session = sessionBuilder.build();

  setUp(() async {
    await Product.db.insert(session, [
      Product(name: 'Apple', price: 10),
      Product(name: 'Banana', price: 10),
    ]);
  });

  test('then all returns both products', () async {
    final products = await endpoints.products.all(sessionBuilder);
    expect(products, hasLength(2));
  });
});

手動で tearDown を用意する必要はありません。デフォルトでは、各テストはトランザクション内で実行され、ロールバックされます。

ロールバックの動作

デフォルトは RollbackDatabase.afterEach で、各テストはロールバックされるトランザクション内で実行されます。

  • afterAll — グループ内のすべてのテストが終わった後にロールバックします。連続するテストが互いに依存し、セットアップのコストが高いシナリオテストに便利です。
  • disabled — 自動ロールバックを行いません。エンドポイントのコードが並行して session.db.transaction(...) を呼び出す場合に必要です(ネストされたトランザクションは InvalidConfigurationException をスローします)。各 withServerpod グループには専用のデータベースが割り当てられるため、コミット済みのデータが他のグループに漏れることはなく、手動でのクリーンアップや --concurrency=1 は不要です。同じグループ内の後続テストでクリーンな状態が必要な場合に限り、tearDown でクリーンアップしてください。
withServerpod(
  'Given concurrent transactions',
  (sessionBuilder, endpoints) {
    test('then should commit all', () async {
      await endpoints.products.concurrentTransactionCalls(sessionBuilder);
    });
  },
  rollbackDatabase: RollbackDatabase.disabled,
);

Session を使ったビジネスロジックのテスト

ロジックがエンドポイントの外にあり、Session が必要な場合は、withServerpod を使い、エンドポイントのパラメーターを無視してください。

withServerpod('Given product quantity is zero', (sessionBuilder, _) {
  var session = sessionBuilder.build();

  setUp(() async {
    await Product.db.insertRow(session, Product(id: 123, name: 'Apple', quantity: 0));
  });

  test('then decreasing throws', () async {
    await expectLater(
      ProductsBusinessLogic.updateQuantity(session, id: 123, decrease: 1),
      throwsA(isA<InvalidOperationException>()),
    );
  });
});

ストリームのテスト

アサーションを行う前にジェネレーターが yield まで実行されるよう、flushEventQueue() を使ってください。

withServerpod('Given shared stream', (sessionBuilder, endpoints) {
  final user1 = sessionBuilder.copyWith(
    authentication: AuthenticationOverride.authenticationInfo('user-1', {}));
  final user2 = sessionBuilder.copyWith(
    authentication: AuthenticationOverride.authenticationInfo('user-2', {}));

  test('when posting numbers then listener receives them', () async {
    var stream = endpoints.comm.listenForNumbers(user1);
    await flushEventQueue(); // Wait for stream to register

    await endpoints.comm.postNumber(user2, 111);
    await endpoints.comm.postNumber(user2, 222);

    await expectLater(stream.take(2), emitsInOrder([111, 222]));
  });
});

withServerpod のオプション

動作を変更する機会が多いオプションは次のとおりです。

  • rollbackDatabase — afterEach(デフォルト)、afterAll、または disabled。
  • ephemeralDatabase — グループごとに専用の空のデータベースを使います。デフォルトは true です。設定済みのデータベース(以前にデータを投入したものを含む)を使うには、false に設定します。このデータベースは作成も削除もされません。また、このデータベースを共有するグループは並列実行できません。
  • applyMigrations — 起動時に未適用のマイグレーションを適用します。デフォルトは true です。
  • runMode — デフォルトは ServerpodRunMode.test で、config/test.yaml が読み込まれます。
  • configOverride — 読み込んだ設定を調整します。たとえば、別のデータベースを指定できます。

デフォルト値を含む全オプションは、references/with-serverpod-options.md を参照してください。

テストの実行

dart test                     # All tests
dart test -t integration      # Only integration tests
dart test -x integration      # Only unit tests

事前に起動しておくものはありません。docker compose up は実行しないでください。 withServerpod は test 実行モードでサーバーを起動し、テスト用データベースも併せて用意します。serverpod create で作成したプロジェクトでは、Serverpod が起動して管理する組み込み PostgreSQL、または SQLite ファイルが使われます。これらのプロジェクトにも生成された docker-compose.yaml が存在しますが、そのファイルがあるからといって Docker を起動する必要はありません。

実行時に実際にデータベースへの接続に失敗した場合に限り、config/test.yaml を確認してください。database セクションに host/port があり、dataPath がない場合は外部データベースを指しており、起動しておく必要があります(docker compose up -d を実行すると、プロジェクトに含まれるデータベースが起動します)。dataPath または filePath が設定されている場合は、設定または実行環境を修正してください。Docker を起動しても解決しません。

プロジェクト構成

テストを整理して配置してください。

  • test/unit/ — 単体テスト(Serverpod への依存なし)
  • test/integration/ — withServerpod を使うテスト

エンドポイントクラスを直接インスタンス化するのではなく、必ず endpoints パラメーターを通じてエンドポイントを呼び出してください。テストツールがライフサイクルとバリデーションを管理し、本番環境と同じ動作を再現します。

コメント