mcpField
E
encore-go-testing

encore-go-testing

github.com/encoredevテスト・品質30570 回インストール2 日前に更新
リポジトリ

encore-go-testing について

標準の Go テストと Encore CLI を使って Encore Go アプリをテストするガイドです。エンドポイントやインフラのテスト、分離されたデータベース、サービス分離、トレース、モックを扱います。

主な機能

  1. 01encore test によるテスト実行
  2. 02API とサービス呼び出しのテスト
  3. 03分離されたテストデータベースの利用
  4. 04サービスインスタンス分離の有効化
  5. 05ダッシュボードでのテストトレース確認
  6. 06エンドポイントとサービスのモック

活用シーン

  1. 01テスト用インフラを使ったテスト実行
  2. 02データベース操作の分離検証
  3. 03テストトレースによる障害の診断
  4. 04モックしたエンドポイントでのコード検証

インストール

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

$npx skills add https://github.com/encoredev/skills --skill encore-go-testing

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

詳細

Encore Go アプリケーションのテスト

手順

Encore Go は encore test による標準的な Go テストを使用します。

テストの実行
# Run all tests with Encore (recommended)
encore test ./...

# Run tests for a specific package
encore test ./user/...

# Run with verbose output
encore test -v ./...

go test ではなく encore test の使用が推奨されます。その理由は次のとおりです。

  • テストデータベースを自動的にセットアップします
  • テストごとに分離されたインフラストラクチャを提供します
  • サービスの依存関係を処理します
API エンドポイントのテスト
// hello/hello_test.go
package hello

import (
    "context"
    "testing"
)

func TestHello(t *testing.T) {
    ctx := context.Background()
    
    resp, err := Hello(ctx)
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    
    if resp.Message != "Hello, World!" {
        t.Errorf("expected 'Hello, World!', got '%s'", resp.Message)
    }
}
リクエストパラメータを使用したテスト
// user/user_test.go
package user

import (
    "context"
    "testing"
)

func TestGetUser(t *testing.T) {
    ctx := context.Background()
    
    user, err := GetUser(ctx, &GetUserParams{ID: "123"})
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    
    if user.ID != "123" {
        t.Errorf("expected ID '123', got '%s'", user.ID)
    }
}
データベース操作のテスト

Encore は分離されたテストデータベースを提供します。

// user/user_test.go
package user

import (
    "context"
    "testing"
    
    "encore.dev/storage/sqldb"
)

func TestCreateUser(t *testing.T) {
    ctx := context.Background()
    
    // Clean up
    _, _ = sqldb.Exec(ctx, db, "DELETE FROM users")
    
    // Create user
    created, err := CreateUser(ctx, &CreateUserParams{
        Email: "[email protected]",
        Name:  "Test User",
    })
    if err != nil {
        t.Fatalf("failed to create user: %v", err)
    }
    
    // Retrieve and verify
    retrieved, err := GetUser(ctx, &GetUserParams{ID: created.ID})
    if err != nil {
        t.Fatalf("failed to get user: %v", err)
    }
    
    if retrieved.Email != "[email protected]" {
        t.Errorf("expected email '[email protected]', got '%s'", retrieved.Email)
    }
}
サービス間呼び出しのテスト
// order/order_test.go
package order

import (
    "context"
    "testing"
)

func TestCreateOrder(t *testing.T) {
    ctx := context.Background()
    
    // Service calls work normally in tests
    order, err := CreateOrder(ctx, &CreateOrderParams{
        UserID: "user-123",
        Items: []OrderItem{
            {ProductID: "prod-1", Quantity: 2},
        },
    })
    if err != nil {
        t.Fatalf("failed to create order: %v", err)
    }
    
    if order.Status != "pending" {
        t.Errorf("expected status 'pending', got '%s'", order.Status)
    }
}
エラーケースのテスト
package user

import (
    "context"
    "errors"
    "testing"
    
    "encore.dev/beta/errs"
)

func TestGetUser_NotFound(t *testing.T) {
    ctx := context.Background()
    
    _, err := GetUser(ctx, &GetUserParams{ID: "nonexistent"})
    if err == nil {
        t.Fatal("expected error, got nil")
    }
    
    // Check error code
    var e *errs.Error
    if errors.As(err, &e) {
        if e.Code != errs.NotFound {
            t.Errorf("expected NotFound, got %v", e.Code)
        }
    } else {
        t.Errorf("expected errs.Error, got %T", err)
    }
}
Pub/Sub のテスト
// notifications/notifications_test.go
package notifications

import (
    "context"
    "testing"
    
    "myapp/events"
)

func TestPublishOrderCreated(t *testing.T) {
    ctx := context.Background()
    
    msgID, err := events.OrderCreated.Publish(ctx, &events.OrderCreatedEvent{
        OrderID: "order-123",
        UserID:  "user-456",
        Total:   9999,
    })
    if err != nil {
        t.Fatalf("failed to publish: %v", err)
    }
    
    if msgID == "" {
        t.Error("expected message ID, got empty string")
    }
}
Cron ジョブのテスト

cron スケジュールではなく、基になる関数をテストします。

// cleanup/cleanup_test.go
package cleanup

import (
    "context"
    "testing"
)

func TestCleanupExpiredSessions(t *testing.T) {
    ctx := context.Background()
    
    // Create some expired sessions first
    createExpiredSession(ctx)
    
    // Call the endpoint directly
    err := CleanupExpiredSessions(ctx)
    if err != nil {
        t.Fatalf("cleanup failed: %v", err)
    }
    
    // Verify cleanup happened
    count := countSessions(ctx)
    if count != 0 {
        t.Errorf("expected 0 sessions, got %d", count)
    }
}
テーブル駆動テスト
func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name    string
        email   string
        wantErr bool
    }{
        {"valid email", "[email protected]", false},
        {"missing @", "userexample.com", true},
        {"empty", "", true},
        {"valid with subdomain", "[email protected]", false},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := validateEmail(tt.email)
            if (err != nil) != tt.wantErr {
                t.Errorf("validateEmail(%q) error = %v, wantErr %v", tt.email, err, tt.wantErr)
            }
        })
    }
}
サブテストを使用したテスト
func TestUserCRUD(t *testing.T) {
    ctx := context.Background()
    var userID string
    
    t.Run("create", func(t *testing.T) {
        user, err := CreateUser(ctx, &CreateUserParams{
            Email: "[email protected]",
            Name:  "Test",
        })
        if err != nil {
            t.Fatalf("create failed: %v", err)
        }
        userID = user.ID
    })
    
    t.Run("read", func(t *testing.T) {
        user, err := GetUser(ctx, &GetUserParams{ID: userID})
        if err != nil {
            t.Fatalf("read failed: %v", err)
        }
        if user.Email != "[email protected]" {
            t.Errorf("wrong email: %s", user.Email)
        }
    })
    
    t.Run("delete", func(t *testing.T) {
        err := DeleteUser(ctx, &DeleteUserParams{ID: userID})
        if err != nil {
            t.Fatalf("delete failed: %v", err)
        }
    })
}
テストデータベースの分離

et.NewTestDatabase() を使用して、分離された完全にマイグレーション済みのテストデータベースを作成します。

import "encore.dev/et"

func TestWithFreshDatabase(t *testing.T) {
    // Creates a new database with all migrations applied
    testDB := et.NewTestDatabase(t, db)

    // Use testDB for queries - it's completely isolated
    _, err := testDB.Exec(ctx, "INSERT INTO users (email) VALUES ($1)", "[email protected]")
    if err != nil {
        t.Fatal(err)
    }
}
サービスインスタンスの分離

デフォルトでは、パフォーマンスのためにサービスの構造体はテスト間で共有されます。テストがサービス状態を変更する場合は分離を有効にします。

import "encore.dev/et"

func TestWithServiceIsolation(t *testing.T) {
    // Enable service instance isolation for this test
    et.EnableServiceInstanceIsolation()

    // Now this test gets its own service struct instance
    // preventing state interference with other tests
}
テストトレーシングダッシュボード

テストの実行中に、開発ダッシュボードの http://localhost:9400 でテスト実行トレースを表示できます。これは次の情報を表示することで障害の診断に役立ちます。

  • リクエスト/レスポンスデータ
  • データベースクエリ
  • サービス間呼び出し
  • エラーとスタックトレース
エンドポイントとサービスのモック

分離された単体テストのために、エンドポイントまたはサービス全体をモックします。

import "encore.dev/et"

func TestWithMockedEndpoint(t *testing.T) {
    // Mock a specific endpoint
    et.MockEndpoint(products.GetPrice, func(ctx context.Context, p *products.PriceParams) (*products.PriceResponse, error) {
        return &products.PriceResponse{Price: 100}, nil
    })

    // Mock an entire service
    et.MockService("products", &mockProductService{})
}
ガイドライン
  • インフラストラクチャのセットアップを伴うテストの実行には encore test を使用します
  • 各テストは実際のインフラストラクチャ(データベース、Pub/Sub)にアクセスできます
  • API エンドポイントは関数として直接呼び出すことでテストします
  • サービス間呼び出しはテスト内で通常どおり動作します
  • 複数のケースをテストするにはテーブル駆動テストを使用します
  • 分離されたデータベーステストには et.NewTestDatabase() を使用します
  • テストがサービス状態を変更する場合は et.EnableServiceInstanceIsolation() を使用します
  • Encore のインフラストラクチャをモックしないでください。実際のものを使用してください
  • 外部依存関係(サードパーティ API、メールサービスなど)はモックします

コメント