关于 encore-go-testing
介绍如何使用标准 Go 测试和 Encore CLI 测试 Encore Go 应用,涵盖端点与基础设施测试、隔离数据库、服务隔离、跟踪查看和模拟。
主要功能
- 01使用 encore test 运行测试
- 02测试 API 和服务调用
- 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 使用标准 Go 测试,并通过 encore test 运行。
运行测试
# Run all tests with Encore (recommended)
encore test ./...
# Run tests for a specific package
encore test ./user/...
# Run with verbose output
encore test -v ./...
建议使用 encore test 而不是 go 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