Documentation

docs
contributing
testing

Testing Infrastructure

Testing Infrastructure

Planned feature

The pkg/testing helpers described below are planned. Use standard go test, fiber.Test(), and httptest as shown in the framework's internal/auth/handlers_test.go and routes/core_test.go.

Mithril uses Go's built-in testing package. Run tests with:

Testing Utilities#

Test Helper

// pkg/testing/helper.go
package testing

import (
    "bytes"
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"
    "github.com/gofiber/fiber/v2"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

type TestHelper struct {
    App    *fiber.App
    Server *httptest.Server
    t      *testing.T
}

func NewTestHelper(t *testing.T) *TestHelper {
    app := fiber.New()
    
    return &TestHelper{
        App:    app,
        Server: httptest.NewServer(app),
        t:      t,
    }
}

func (h *TestHelper) Cleanup() {
    h.Server.Close()
}

func (h *TestHelper) SetupTestRoutes() {
    // Setup test routes
    h.App.Get("/test", func(c *fiber.Ctx) error {
        return c.JSON(fiber.Map{"message": "test"})
    })
    
    h.App.Post("/test", func(c *fiber.Ctx) error {
        var body map[string]interface{}
        if err := c.BodyParser(&body); err != nil {
            return c.Status(400).JSON(fiber.Map{"error": err.Error()})
        }
        return c.JSON(body)
    })
}

func (h *TestHelper) Get(path string) *TestResponse {
    return h.Request("GET", path, nil, nil)
}

func (h *TestHelper) Post(path string, body interface{}) *TestResponse {
    return h.Request("POST", path, body, nil)
}

func (h *TestHelper) Put(path string, body interface{}) *TestResponse {
    return h.Request("PUT", path, body, nil)
}

func (h *TestHelper) Delete(path string) *TestResponse {
    return h.Request("DELETE", path, nil, nil)
}

func (h *TestHelper) Request(method, path string, body interface{}, headers map[string]string) *TestResponse {
    var reqBody []byte
    var err error
    
    if body != nil {
        reqBody, err = json.Marshal(body)
        require.NoError(h.t, err)
    }
    
    req, err := http.NewRequest(method, h.Server.URL+path, bytes.NewReader(reqBody))
    require.NoError(h.t, err)
    
    if body != nil {
        req.Header.Set("Content-Type", "application/json")
    }
    
    for key, value := range headers {
        req.Header.Set(key, value)
    }
    
    resp, err := http.DefaultClient.Do(req)
    require.NoError(h.t, err)
    
    return &TestResponse{
        Response: resp,
        t:        h.t,
    }
}

type TestResponse struct {
    *http.Response
    t *testing.T
}

func (r *TestResponse) AssertStatus(t *testing.T, expected int) {
    assert.Equal(t, expected, r.StatusCode)
}

func (r *TestResponse) AssertJSON(t *testing.T, expected interface{}) {
    var actual interface{}
    err := json.NewDecoder(r.Body).Decode(&actual)
    require.NoError(t, err)
    
    assert.Equal(t, expected, actual)
}

func (r *TestResponse) AssertHeader(t *testing.T, key, expected string) {
    assert.Equal(t, expected, r.Header.Get(key))
}

func (r *TestResponse) AssertContentType(t *testing.T, expected string) {
    assert.Equal(t, expected, r.Header.Get("Content-Type"))
}

func (r *TestResponse) Body() string {
    buf := new(bytes.Buffer)
    buf.ReadFrom(r.Body)
    return buf.String()
}

Database Testing

// pkg/testing/database.go
package testing

import (
    "database/sql"
    "testing"
    "github.com/stretchr/testify/require"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

type DatabaseTestHelper struct {
    DB *gorm.DB
    t  *testing.T
}

func NewDatabaseTestHelper(t *testing.T) *DatabaseTestHelper {
    // Use in-memory SQLite for testing
    db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
    require.NoError(t, err)
    
    return &DatabaseTestHelper{
        DB: db,
        t:  t,
    }
}

func (h *DatabaseTestHelper) Setup() {
    // Run migrations
    err := h.DB.AutoMigrate(&models.User{}, &models.Post{})
    require.NoError(h.t, err)
}

func (h *DatabaseTestHelper) Cleanup() {
    sqlDB, err := h.DB.DB()
    require.NoError(h.t, err)
    sqlDB.Close()
}

func (h *DatabaseTestHelper) SeedUsers(count int) []models.User {
    users := make([]models.User, count)
    for i := 0; i < count; i++ {
        users[i] = models.User{
            Email: fmt.Sprintf("user%d@example.com", i),
            Name:  fmt.Sprintf("User %d", i),
        }
    }
    
    err := h.DB.Create(&users).Error
    require.NoError(h.t, err)
    
    return users
}

func (h *DatabaseTestHelper) SeedPosts(userID uint, count int) []models.Post {
    posts := make([]models.Post, count)
    for i := 0; i < count; i++ {
        posts[i] = models.Post{
            Title:   fmt.Sprintf("Post %d", i),
            Content: fmt.Sprintf("Content for post %d", i),
            UserID:  userID,
        }
    }
    
    err := h.DB.Create(&posts).Error
    require.NoError(h.t, err)
    
    return posts
}

func (h *DatabaseTestHelper) TruncateTables(tables ...string) {
    for _, table := range tables {
        err := h.DB.Exec("DELETE FROM " + table).Error
        require.NoError(h.t, err)
    }
}

Mock Services

// pkg/testing/mocks.go
package testing

import (
    "context"
    "errors"
    "my-app/app/services"
)

// MockUserService implements the UserService interface for testing
type MockUserService struct {
    users map[uint]*models.User
    nextID uint
}

func NewMockUserService() *MockUserService {
    return &MockUserService{
        users: make(map[uint]*models.User),
        nextID: 1,
    }
}

func (m *MockUserService) GetByID(ctx context.Context, id uint) (*models.User, error) {
    user, exists := m.users[id]
    if !exists {
        return nil, errors.New("user not found")
    }
    return user, nil
}

func (m *MockUserService) Create(ctx context.Context, req services.CreateUserRequest) (*models.User, error) {
    user := &models.User{
        ID:    m.nextID,
        Email: req.Email,
        Name:  req.Name,
    }
    m.users[m.nextID] = user
    m.nextID++
    return user, nil
}

func (m *MockUserService) Update(ctx context.Context, id uint, req services.UpdateUserRequest) (*models.User, error) {
    user, exists := m.users[id]
    if !exists {
        return nil, errors.New("user not found")
    }
    
    if req.Name != "" {
        user.Name = req.Name
    }
    
    return user, nil
}

func (m *MockUserService) Delete(ctx context.Context, id uint) error {
    if _, exists := m.users[id]; !exists {
        return errors.New("user not found")
    }
    delete(m.users, id)
    return nil
}

func (m *MockUserService) List(ctx context.Context, page, perPage int) ([]*models.User, int64, error) {
    users := make([]*models.User, 0, len(m.users))
    for _, user := range m.users {
        users = append(users, user)
    }
    
    total := int64(len(users))
    start := (page - 1) * perPage
    end := start + perPage
    
    if start >= len(users) {
        return []*models.User{}, total, nil
    }
    
    if end > len(users) {
        end = len(users)
    }
    
    return users[start:end], total, nil
}

// MockEmailService implements the EmailService interface for testing
type MockEmailService struct {
    sentEmails []EmailMessage
}

type EmailMessage struct {
    To      string
    Subject string
    Body    string
}

func NewMockEmailService() *MockEmailService {
    return &MockEmailService{
        sentEmails: make([]EmailMessage, 0),
    }
}

func (m *MockEmailService) Send(ctx context.Context, to, subject, body string) error {
    m.sentEmails = append(m.sentEmails, EmailMessage{
        To:      to,
        Subject: subject,
        Body:    body,
    })
    return nil
}

func (m *MockEmailService) GetSentEmails() []EmailMessage {
    return m.sentEmails
}

func (m *MockEmailService) Clear() {
    m.sentEmails = make([]EmailMessage, 0)
}

Unit Testing#

Controller Testing

// app/controllers/user_controller_test.go
package controllers

import (
    "context"
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
    "my-app/pkg/testing"
    "my-app/app/schemas"
)

func TestUserController_Create(t *testing.T) {
    helper := testing.NewTestHelper(t)
    defer helper.Cleanup()
    
    // Setup
    mockUserService := testing.NewMockUserService()
    controller := NewUserController(mockUserService)
    
    helper.App.Post("/users", controller.Create)
    
    // Test data
    userData := schemas.CreateUserRequest{
        Email: "test@example.com",
        Name:  "Test User",
        Age:   25,
    }
    
    // Execute
    resp := helper.Post("/users", userData)
    
    // Assert
    resp.AssertStatus(t, 201)
    resp.AssertContentType(t, "application/json")
    
    var response map[string]interface{}
    err := json.NewDecoder(resp.Body).Decode(&response)
    require.NoError(t, err)
    
    assert.Equal(t, "test@example.com", response["email"])
    assert.Equal(t, "Test User", response["name"])
    assert.Equal(t, float64(25), response["age"])
}

func TestUserController_GetByID(t *testing.T) {
    helper := testing.NewTestHelper(t)
    defer helper.Cleanup()
    
    // Setup
    mockUserService := testing.NewMockUserService()
    controller := NewUserController(mockUserService)
    
    // Create a user first
    user, err := mockUserService.Create(context.Background(), services.CreateUserRequest{
        Email: "test@example.com",
        Name:  "Test User",
    })
    require.NoError(t, err)
    
    helper.App.Get("/users/:id", controller.Show)
    
    // Execute
    resp := helper.Get(fmt.Sprintf("/users/%d", user.ID))
    
    // Assert
    resp.AssertStatus(t, 200)
    
    var response map[string]interface{}
    err = json.NewDecoder(resp.Body).Decode(&response)
    require.NoError(t, err)
    
    assert.Equal(t, float64(user.ID), response["id"])
    assert.Equal(t, "test@example.com", response["email"])
    assert.Equal(t, "Test User", response["name"])
}

func TestUserController_Update(t *testing.T) {
    helper := testing.NewTestHelper(t)
    defer helper.Cleanup()
    
    // Setup
    mockUserService := testing.NewMockUserService()
    controller := NewUserController(mockUserService)
    
    // Create a user first
    user, err := mockUserService.Create(context.Background(), services.CreateUserRequest{
        Email: "test@example.com",
        Name:  "Test User",
    })
    require.NoError(t, err)
    
    helper.App.Put("/users/:id", controller.Update)
    
    // Test data
    updateData := schemas.UpdateUserRequest{
        Name: "Updated User",
    }
    
    // Execute
    resp := helper.Put(fmt.Sprintf("/users/%d", user.ID), updateData)
    
    // Assert
    resp.AssertStatus(t, 200)
    
    var response map[string]interface{}
    err = json.NewDecoder(resp.Body).Decode(&response)
    require.NoError(t, err)
    
    assert.Equal(t, "Updated User", response["name"])
    assert.Equal(t, "test@example.com", response["email"]) // Should remain unchanged
}

func TestUserController_Delete(t *testing.T) {
    helper := testing.NewTestHelper(t)
    defer helper.Cleanup()
    
    // Setup
    mockUserService := testing.NewMockUserService()
    controller := NewUserController(mockUserService)
    
    // Create a user first
    user, err := mockUserService.Create(context.Background(), services.CreateUserRequest{
        Email: "test@example.com",
        Name:  "Test User",
    })
    require.NoError(t, err)
    
    helper.App.Delete("/users/:id", controller.Delete)
    
    // Execute
    resp := helper.Delete(fmt.Sprintf("/users/%d", user.ID))
    
    // Assert
    resp.AssertStatus(t, 204)
    
    // Verify user is deleted
    _, err = mockUserService.GetByID(context.Background(), user.ID)
    assert.Error(t, err)
}

Service Testing

// app/services/user_service_test.go
package services

import (
    "context"
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
    "my-app/pkg/testing"
)

func TestUserService_Create(t *testing.T) {
    dbHelper := testing.NewDatabaseTestHelper(t)
    defer dbHelper.Cleanup()
    
    dbHelper.Setup()
    
    userService := NewUserService(dbHelper.DB)
    
    req := CreateUserRequest{
        Email:    "test@example.com",
        Name:     "Test User",
        Password: "password123",
        Age:      25,
    }
    
    user, err := userService.Create(context.Background(), req)
    
    require.NoError(t, err)
    assert.Equal(t, "test@example.com", user.Email)
    assert.Equal(t, "Test User", user.Name)
    assert.Equal(t, 25, user.Age)
    assert.NotEmpty(t, user.Password) // Should be hashed
    assert.NotZero(t, user.ID)
    assert.NotZero(t, user.CreatedAt)
    assert.NotZero(t, user.UpdatedAt)
}

func TestUserService_GetByID(t *testing.T) {
    dbHelper := testing.NewDatabaseTestHelper(t)
    defer dbHelper.Cleanup()
    
    dbHelper.Setup()
    
    userService := NewUserService(dbHelper.DB)
    
    // Create a user first
    user, err := userService.Create(context.Background(), CreateUserRequest{
        Email:    "test@example.com",
        Name:     "Test User",
        Password: "password123",
    })
    require.NoError(t, err)
    
    // Get the user
    retrievedUser, err := userService.GetByID(context.Background(), user.ID)
    
    require.NoError(t, err)
    assert.Equal(t, user.ID, retrievedUser.ID)
    assert.Equal(t, user.Email, retrievedUser.Email)
    assert.Equal(t, user.Name, retrievedUser.Name)
}

func TestUserService_Update(t *testing.T) {
    dbHelper := testing.NewDatabaseTestHelper(t)
    defer dbHelper.Cleanup()
    
    dbHelper.Setup()
    
    userService := NewUserService(dbHelper.DB)
    
    // Create a user first
    user, err := userService.Create(context.Background(), CreateUserRequest{
        Email:    "test@example.com",
        Name:     "Test User",
        Password: "password123",
    })
    require.NoError(t, err)
    
    // Update the user
    updateReq := UpdateUserRequest{
        Name: "Updated User",
        Age:  30,
    }
    
    updatedUser, err := userService.Update(context.Background(), user.ID, updateReq)
    
    require.NoError(t, err)
    assert.Equal(t, "Updated User", updatedUser.Name)
    assert.Equal(t, 30, updatedUser.Age)
    assert.Equal(t, user.Email, updatedUser.Email) // Should remain unchanged
}

func TestUserService_Delete(t *testing.T) {
    dbHelper := testing.NewDatabaseTestHelper(t)
    defer dbHelper.Cleanup()
    
    dbHelper.Setup()
    
    userService := NewUserService(dbHelper.DB)
    
    // Create a user first
    user, err := userService.Create(context.Background(), CreateUserRequest{
        Email:    "test@example.com",
        Name:     "Test User",
        Password: "password123",
    })
    require.NoError(t, err)
    
    // Delete the user
    err = userService.Delete(context.Background(), user.ID)
    require.NoError(t, err)
    
    // Try to get the user (should fail)
    _, err = userService.GetByID(context.Background(), user.ID)
    assert.Error(t, err)
}

Integration Testing#

API Integration Tests

// tests/integration/api_test.go
package integration

import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
    "my-app/pkg/testing"
)

func TestUserAPI_Integration(t *testing.T) {
    helper := testing.NewTestHelper(t)
    defer helper.Cleanup()
    
    // Setup database
    dbHelper := testing.NewDatabaseTestHelper(t)
    defer dbHelper.Cleanup()
    dbHelper.Setup()
    
    // Setup services
    userService := services.NewUserService(dbHelper.DB)
    emailService := testing.NewMockEmailService()
    
    // Setup controllers
    userController := controllers.NewUserController(userService)
    authController := controllers.NewAuthController(userService, emailService)
    
    // Setup routes
    helper.App.Post("/auth/register", authController.Register)
    helper.App.Post("/auth/login", authController.Login)
    helper.App.Get("/users/:id", middleware.Auth(), userController.Show)
    helper.App.Put("/users/:id", middleware.Auth(), userController.Update)
    helper.App.Delete("/users/:id", middleware.Auth(), userController.Delete)
    
    // Test registration
    registerData := schemas.CreateUserRequest{
        Email:    "test@example.com",
        Name:     "Test User",
        Password: "password123",
        Age:      25,
    }
    
    resp := helper.Post("/auth/register", registerData)
    resp.AssertStatus(t, 201)
    
    // Test login
    loginData := schemas.LoginRequest{
        Email:    "test@example.com",
        Password: "password123",
    }
    
    resp = helper.Post("/auth/login", loginData)
    resp.AssertStatus(t, 200)
    
    var loginResponse map[string]interface{}
    err := json.NewDecoder(resp.Body).Decode(&loginResponse)
    require.NoError(t, err)
    
    token := loginResponse["token"].(string)
    assert.NotEmpty(t, token)
    
    // Test authenticated requests
    headers := map[string]string{
        "Authorization": "Bearer " + token,
    }
    
    // Get user profile
    resp = helper.Request("GET", "/users/1", nil, headers)
    resp.AssertStatus(t, 200)
    
    // Update user
    updateData := schemas.UpdateUserRequest{
        Name: "Updated User",
        Age:  30,
    }
    
    resp = helper.Request("PUT", "/users/1", updateData, headers)
    resp.AssertStatus(t, 200)
    
    // Verify update
    resp = helper.Request("GET", "/users/1", nil, headers)
    resp.AssertStatus(t, 200)
    
    var userResponse map[string]interface{}
    err = json.NewDecoder(resp.Body).Decode(&userResponse)
    require.NoError(t, err)
    
    assert.Equal(t, "Updated User", userResponse["name"])
    assert.Equal(t, float64(30), userResponse["age"])
}

Database Integration Tests

// tests/integration/database_test.go
package integration

import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
    "my-app/pkg/testing"
)

func TestDatabase_Integration(t *testing.T) {
    dbHelper := testing.NewDatabaseTestHelper(t)
    defer dbHelper.Cleanup()
    
    dbHelper.Setup()
    
    // Test user creation and relationships
    userService := services.NewUserService(dbHelper.DB)
    postService := services.NewPostService(dbHelper.DB)
    
    // Create user
    user, err := userService.Create(context.Background(), services.CreateUserRequest{
        Email:    "test@example.com",
        Name:     "Test User",
        Password: "password123",
    })
    require.NoError(t, err)
    
    // Create posts for user
    posts := []services.CreatePostRequest{
        {Title: "Post 1", Content: "Content 1", UserID: user.ID},
        {Title: "Post 2", Content: "Content 2", UserID: user.ID},
        {Title: "Post 3", Content: "Content 3", UserID: user.ID},
    }
    
    for _, postReq := range posts {
        _, err := postService.Create(context.Background(), postReq)
        require.NoError(t, err)
    }
    
    // Test user with posts
    userWithPosts, err := userService.GetWithPosts(context.Background(), user.ID)
    require.NoError(t, err)
    
    assert.Equal(t, 3, len(userWithPosts.Posts))
    assert.Equal(t, "Post 1", userWithPosts.Posts[0].Title)
    assert.Equal(t, "Post 2", userWithPosts.Posts[1].Title)
    assert.Equal(t, "Post 3", userWithPosts.Posts[2].Title)
    
    // Test post with user
    post, err := postService.GetWithUser(context.Background(), userWithPosts.Posts[0].ID)
    require.NoError(t, err)
    
    assert.Equal(t, "Post 1", post.Title)
    assert.Equal(t, user.ID, post.User.ID)
    assert.Equal(t, "Test User", post.User.Name)
}

End-to-End Testing#

E2E Test Suite

// tests/e2e/e2e_test.go
package e2e

import (
    "testing"
    "time"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
    "my-app/pkg/testing"
)

type E2ETestSuite struct {
    helper *testing.TestHelper
    dbHelper *testing.DatabaseTestHelper
    userService *services.UserService
    emailService *testing.MockEmailService
}

func NewE2ETestSuite(t *testing.T) *E2ETestSuite {
    helper := testing.NewTestHelper(t)
    dbHelper := testing.NewDatabaseTestHelper(t)
    dbHelper.Setup()
    
    userService := services.NewUserService(dbHelper.DB)
    emailService := testing.NewMockEmailService()
    
    return &E2ETestSuite{
        helper:       helper,
        dbHelper:     dbHelper,
        userService:  userService,
        emailService: emailService,
    }
}

func (s *E2ETestSuite) Close() {
    s.helper.Cleanup()
    s.dbHelper.Cleanup()
}

func (s *E2ETestSuite) SetupRoutes() {
    // Setup all routes
    s.helper.App.Post("/auth/register", s.authController.Register)
    s.helper.App.Post("/auth/login", s.authController.Login)
    s.helper.App.Get("/users", middleware.Auth(), s.userController.Index)
    s.helper.App.Post("/users", middleware.Auth(), s.userController.Create)
    s.helper.App.Get("/users/:id", middleware.Auth(), s.userController.Show)
    s.helper.App.Put("/users/:id", middleware.Auth(), s.userController.Update)
    s.helper.App.Delete("/users/:id", middleware.Auth(), s.userController.Delete)
}

func TestE2E_CompleteUserWorkflow(t *testing.T) {
    suite := NewE2ETestSuite(t)
    defer suite.Close()
    
    suite.SetupRoutes()
    
    // Step 1: Register a new user
    registerData := schemas.CreateUserRequest{
        Email:    "test@example.com",
        Name:     "Test User",
        Password: "password123",
        Age:      25,
    }
    
    resp := suite.helper.Post("/auth/register", registerData)
    resp.AssertStatus(t, 201)
    
    // Verify email was sent
    emails := suite.emailService.GetSentEmails()
    assert.Len(t, emails, 1)
    assert.Equal(t, "test@example.com", emails[0].To)
    assert.Contains(t, emails[0].Subject, "Welcome")
    
    // Step 2: Login
    loginData := schemas.LoginRequest{
        Email:    "test@example.com",
        Password: "password123",
    }
    
    resp = suite.helper.Post("/auth/login", loginData)
    resp.AssertStatus(t, 200)
    
    var loginResponse map[string]interface{}
    err := json.NewDecoder(resp.Body).Decode(&loginResponse)
    require.NoError(t, err)
    
    token := loginResponse["token"].(string)
    assert.NotEmpty(t, token)
    
    // Step 3: Get user profile
    headers := map[string]string{
        "Authorization": "Bearer " + token,
    }
    
    resp = suite.helper.Request("GET", "/users/1", nil, headers)
    resp.AssertStatus(t, 200)
    
    var userResponse map[string]interface{}
    err = json.NewDecoder(resp.Body).Decode(&userResponse)
    require.NoError(t, err)
    
    assert.Equal(t, "test@example.com", userResponse["email"])
    assert.Equal(t, "Test User", userResponse["name"])
    assert.Equal(t, float64(25), userResponse["age"])
    
    // Step 4: Update user profile
    updateData := schemas.UpdateUserRequest{
        Name: "Updated User",
        Age:  30,
    }
    
    resp = suite.helper.Request("PUT", "/users/1", updateData, headers)
    resp.AssertStatus(t, 200)
    
    // Step 5: Verify update
    resp = suite.helper.Request("GET", "/users/1", nil, headers)
    resp.AssertStatus(t, 200)
    
    err = json.NewDecoder(resp.Body).Decode(&userResponse)
    require.NoError(t, err)
    
    assert.Equal(t, "Updated User", userResponse["name"])
    assert.Equal(t, float64(30), userResponse["age"])
    
    // Step 6: Create another user
    registerData2 := schemas.CreateUserRequest{
        Email:    "test2@example.com",
        Name:     "Test User 2",
        Password: "password123",
        Age:      28,
    }
    
    resp = suite.helper.Post("/auth/register", registerData2)
    resp.AssertStatus(t, 201)
    
    // Step 7: List users
    resp = suite.helper.Request("GET", "/users", nil, headers)
    resp.AssertStatus(t, 200)
    
    var usersResponse map[string]interface{}
    err = json.NewDecoder(resp.Body).Decode(&usersResponse)
    require.NoError(t, err)
    
    users := usersResponse["data"].([]interface{})
    assert.Len(t, users, 2)
    
    // Step 8: Delete user
    resp = suite.helper.Request("DELETE", "/users/2", nil, headers)
    resp.AssertStatus(t, 204)
    
    // Step 9: Verify deletion
    resp = suite.helper.Request("GET", "/users/2", nil, headers)
    resp.AssertStatus(t, 404)
}

Performance Testing#

Load Testing

// tests/performance/load_test.go
package performance

import (
    "testing"
    "sync"
    "time"
    "github.com/stretchr/testify/assert"
    "my-app/pkg/testing"
)

func TestLoad_ConcurrentUsers(t *testing.T) {
    helper := testing.NewTestHelper(t)
    defer helper.Cleanup()
    
    helper.SetupTestRoutes()
    
    const numUsers = 100
    const requestsPerUser = 10
    
    var wg sync.WaitGroup
    var successCount int32
    var errorCount int32
    
    start := time.Now()
    
    for i := 0; i < numUsers; i++ {
        wg.Add(1)
        go func(userID int) {
            defer wg.Done()
            
            for j := 0; j < requestsPerUser; j++ {
                resp := helper.Get("/test")
                if resp.StatusCode == 200 {
                    atomic.AddInt32(&successCount, 1)
                } else {
                    atomic.AddInt32(&errorCount, 1)
                }
            }
        }(i)
    }
    
    wg.Wait()
    duration := time.Since(start)
    
    totalRequests := int32(numUsers * requestsPerUser)
    
    assert.Equal(t, totalRequests, successCount)
    assert.Equal(t, int32(0), errorCount)
    
    t.Logf("Completed %d requests in %v", totalRequests, duration)
    t.Logf("Requests per second: %.2f", float64(totalRequests)/duration.Seconds())
}

Test Configuration#

Test Environment

# Test Configuration
TEST_ENV=testing
TEST_DATABASE_URL=sqlite://:memory:
TEST_LOG_LEVEL=error
TEST_TIMEOUT=30s

# Test Data
TEST_USER_EMAIL=test@example.com
TEST_USER_PASSWORD=password123
TEST_ADMIN_EMAIL=admin@example.com
TEST_ADMIN_PASSWORD=admin123

Test Setup

// pkg/testing/setup.go
package testing

import (
    "os"
    "testing"
    "github.com/joho/godotenv"
)

func SetupTestEnvironment() {
    // Load test environment variables
    godotenv.Load(".env.test")
    
    // Set test environment
    os.Setenv("APP_ENV", "testing")
    os.Setenv("DB_DRIVER", "sqlite")
    os.Setenv("DB_NAME", ":memory:")
    os.Setenv("LOG_LEVEL", "error")
}

func TestMain(m *testing.M) {
    SetupTestEnvironment()
    code := m.Run()
    os.Exit(code)
}

Best Practices#

1. Test Organization

  • Use descriptive test names
  • Group related tests
  • Use table-driven tests for multiple scenarios
  • Keep tests independent and isolated

2. Test Data

  • Use factories for test data generation
  • Clean up test data after each test
  • Use realistic test data
  • Avoid hardcoded values

3. Mocking

  • Mock external dependencies
  • Use interfaces for better testability
  • Mock at the right level
  • Verify mock interactions

4. Performance

  • Run tests in parallel when possible
  • Use in-memory databases for speed
  • Clean up resources properly
  • Monitor test execution time

5. Maintenance

  • Keep tests up to date with code changes
  • Refactor tests when refactoring code
  • Remove obsolete tests
  • Document complex test scenarios

Next Steps#