Test d'applications Encore Go
Instructions
Encore Go utilise le test standard Go avec encore test.
Exécuter les tests
# Exécuter tous les tests avec Encore (recommandé)
encore test ./...
# Exécuter les tests d'un package spécifique
encore test ./user/...
# Exécuter avec sortie détaillée
encore test -v ./...
L'utilisation de encore test au lieu de go test est recommandée car elle :
- Configure automatiquement les bases de données de test
- Fournit une infrastructure isolée par test
- Gère les dépendances entre services
Tester un endpoint 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)
}
}
Tester avec paramètres de requête
// 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)
}
}
Tester les opérations de base de données
Encore fournit des bases de données de test isolées :
// user/user_test.go
package user
import (
"context"
"testing"
"encore.dev/storage/sqldb"
)
func TestCreateUser(t *testing.T) {
ctx := context.Background()
// Nettoyer
_, _ = sqldb.Exec(ctx, db, "DELETE FROM users")
// Créer un utilisateur
created, err := CreateUser(ctx, &CreateUserParams{
Email: "test@example.com",
Name: "Test User",
})
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
// Récupérer et vérifier
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)
}
}
Tester les appels service-à-service
// order/order_test.go
package order
import (
"context"
"testing"
)
func TestCreateOrder(t *testing.T) {
ctx := context.Background()
// Les appels de service fonctionnent normalement dans les 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)
}
}
Tester les cas d'erreur
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")
}
// Vérifier le code d'erreur
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)
}
}
Tester 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")
}
}
Tester les tâches Cron
Testez la fonction sous-jacente, pas l'horaire cron :
// cleanup/cleanup_test.go
package cleanup
import (
"context"
"testing"
)
func TestCleanupExpiredSessions(t *testing.T) {
ctx := context.Background()
// Créer d'abord quelques sessions expirées
createExpiredSession(ctx)
// Appeler l'endpoint directement
err := CleanupExpiredSessions(ctx)
if err != nil {
t.Fatalf("cleanup failed: %v", err)
}
// Vérifier que le nettoyage a eu lieu
count := countSessions(ctx)
if count != 0 {
t.Errorf("expected 0 sessions, got %d", count)
}
}
Tests pilotés par tableau
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)
}
})
}
}
Tester avec des sous-tests
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)
}
})
}
Isolation des bases de données de test
Créez des bases de données de test isolées et entièrement migrées à l'aide de et.NewTestDatabase() :
import "encore.dev/et"
func TestWithFreshDatabase(t *testing.T) {
// Crée une nouvelle base de données avec toutes les migrations appliquées
testDB := et.NewTestDatabase(t, db)
// Utiliser testDB pour les requêtes - elle est complètement isolée
_, err := testDB.Exec(ctx, "INSERT INTO users (email) VALUES ($1)", "test@example.com")
if err != nil {
t.Fatal(err)
}
}
Isolation de l'instance de service
Par défaut, les structs de service sont partagés entre les tests pour des raisons de performance. Activez l'isolation lorsque les tests modifient l'état du service :
import "encore.dev/et"
func TestWithServiceIsolation(t *testing.T) {
// Activer l'isolation de l'instance de service pour ce test
et.EnableServiceInstanceIsolation()
// Ce test obtient maintenant sa propre instance de struct de service
// évitant les interférences d'état avec les autres tests
}
Tableau de bord de traçage des tests
Consultez les traces d'exécution des tests dans le tableau de bord de développement à http://localhost:9400 pendant l'exécution des tests. Cela aide à diagnostiquer les défaillances en affichant :
- Les données de requête/réponse
- Les requêtes de base de données
- Les appels service-à-service
- Les erreurs et traces de pile
Simuler les endpoints et les services
Simulez les endpoints ou des services entiers pour les tests unitaires isolés :
import "encore.dev/et"
func TestWithMockedEndpoint(t *testing.T) {
// Simuler un endpoint spécifique
et.MockEndpoint(products.GetPrice, func(ctx context.Context, p *products.PriceParams) (*products.PriceResponse, error) {
return &products.PriceResponse{Price: 100}, nil
})
// Simuler un service entier
et.MockService("products", &mockProductService{})
}
Directives
- Utilisez
encore testpour exécuter les tests avec la configuration de l'infrastructure - Chaque test a accès à une infrastructure réelle (bases de données, Pub/Sub)
- Testez les endpoints API en les appelant directement en tant que fonctions
- Les appels service-à-service fonctionnent normalement dans les tests
- Utilisez les tests pilotés par tableau pour tester plusieurs cas
- Utilisez
et.NewTestDatabase()pour les tests de base de données isolés - Utilisez
et.EnableServiceInstanceIsolation()lorsque les tests modifient l'état du service - Ne simulez pas l'infrastructure Encore - utilisez la vraie
- Simulez les dépendances externes (API tierces, services de courrier électronique, etc.)