encore-go-testing について
標準の Go テストと Encore CLI を使って Encore Go アプリをテストするガイドです。エンドポイントやインフラのテスト、分離されたデータベース、サービス分離、トレース、モックを扱います。
主な機能
- 01encore test によるテスト実行
- 02API とサービス呼び出しのテスト
- 03分離されたテストデータベースの利用
- 04サービスインスタンス分離の有効化
- 05ダッシュボードでのテストトレース確認
- 06エンドポイントとサービスのモック
活用シーン
- 01テスト用インフラを使ったテスト実行
- 02データベース操作の分離検証
- 03テストトレースによる障害の診断
- 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、メールサービスなど)はモックします
コメント
Markdown 対応