encore-go-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTesting Encore Go Applications
测试Encore Go应用
Instructions
操作说明
Encore Go uses standard Go testing with .
encore testEncore Go 使用标准的Go测试框架搭配命令。
encore testRun Tests
运行测试
bash
undefinedbash
undefinedRun all tests with Encore (recommended)
Run all tests with Encore (recommended)
encore test ./...
encore test ./...
Run tests for a specific package
Run tests for a specific package
encore test ./user/...
encore test ./user/...
Run with verbose output
Run with verbose output
encore test -v ./...
Using `encore test` instead of `go test` is recommended because it:
- Sets up test databases automatically
- Provides isolated infrastructure per test
- Handles service dependenciesencore test -v ./...
推荐使用`encore test`而非`go test`,原因如下:
- 自动设置测试数据库
- 为每个测试提供独立的基础设施
- 处理服务依赖Test an API Endpoint
测试API端点
go
// 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)
}
}go
// 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)
}
}Test with Request Parameters
带请求参数的测试
go
// 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)
}
}go
// 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)
}
}Test Database Operations
测试数据库操作
Encore provides isolated test databases:
go
// 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: "test@example.com",
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 != "test@example.com" {
t.Errorf("expected email 'test@example.com', got '%s'", retrieved.Email)
}
}Encore提供独立的测试数据库:
go
// 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: "test@example.com",
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 != "test@example.com" {
t.Errorf("expected email 'test@example.com', got '%s'", retrieved.Email)
}
}Test Service-to-Service Calls
测试服务间调用
go
// 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)
}
}go
// 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)
}
}Test Error Cases
测试错误场景
go
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)
}
}go
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)
}
}Test Pub/Sub
测试发布/订阅(Pub/Sub)
go
// 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")
}
}go
// 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")
}
}Test Cron Jobs
测试定时任务(Cron Jobs)
Test the underlying function, not the cron schedule:
go
// 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)
}
}测试底层函数,而非定时调度:
go
// 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)
}
}Table-Driven Tests
表格驱动测试
go
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
wantErr bool
}{
{"valid email", "user@example.com", false},
{"missing @", "userexample.com", true},
{"empty", "", true},
{"valid with subdomain", "user@mail.example.com", 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)
}
})
}
}go
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
wantErr bool
}{
{"valid email", "user@example.com", false},
{"missing @", "userexample.com", true},
{"empty", "", true},
{"valid with subdomain", "user@mail.example.com", 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)
}
})
}
}Test with Subtests
子测试
go
func TestUserCRUD(t *testing.T) {
ctx := context.Background()
var userID string
t.Run("create", func(t *testing.T) {
user, err := CreateUser(ctx, &CreateUserParams{
Email: "test@example.com",
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 != "test@example.com" {
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)
}
})
}go
func TestUserCRUD(t *testing.T) {
ctx := context.Background()
var userID string
t.Run("create", func(t *testing.T) {
user, err := CreateUser(ctx, &CreateUserParams{
Email: "test@example.com",
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 != "test@example.com" {
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)
}
})
}Guidelines
指导原则
- Use to run tests with infrastructure setup
encore test - Each test gets access to real infrastructure (databases, Pub/Sub)
- Test API endpoints by calling them directly as functions
- Service-to-service calls work normally in tests
- Use table-driven tests for testing multiple cases
- Don't mock Encore infrastructure - use the real thing
- Mock external dependencies (third-party APIs, email services, etc.)
- 使用运行测试,自动完成基础设施搭建
encore test - 每个测试都能访问真实的基础设施(数据库、Pub/Sub等)
- 通过直接调用函数来测试API端点
- 服务间调用在测试中正常工作
- 使用表格驱动测试来覆盖多种场景
- 不要Mock Encore基础设施,使用真实的服务
- Mock外部依赖(第三方API、邮件服务等)