Integration Testing
Test how multiple modules and services work together.
Import
import (
"testing"
goosetest "github.com/awesome-goose/goose/testing"
"github.com/awesome-goose/goose/types"
)
What is Integration Testing?
Integration tests verify that different parts of your application work together correctly. Unlike unit tests that isolate individual components, integration tests exercise the interaction between modules, services, and external systems.
When to Write Integration Tests
- Testing module dependencies and imports
- Testing service interactions
- Testing database operations with real connections
- Testing external API integrations
- Testing the full request/response cycle
Skipping Integration Tests
Use test tags to run integration tests only when needed:
import (
"testing"
goosetest "github.com/awesome-goose/goose/testing"
)
func TestIntegration_UserFlow(t *testing.T) {
// Skip unless TEST_LEVEL=integration
goosetest.SkipUnlessIntegration(t)
// Your integration test code...
}
Run integration tests with:
TEST_LEVEL=integration go test ./...
Testing Module Integration
Testing Module Imports
import (
"testing"
goosetest "github.com/awesome-goose/goose/testing"
"myapp/app"
"myapp/app/users"
"myapp/app/orders"
)
func TestAppModule_IntegrationWithSubModules(t *testing.T) {
goosetest.SkipUnlessIntegration(t)
gt := goosetest.NewGooseTest(t)
// Test that AppModule correctly imports all submodules
mt := gt.Module(&app.AppModule{})
imports := mt.TestImports()
gt.Expect(len(imports)).ToBeGreaterThan(0)
// Verify specific modules are imported
hasUsersModule := false
hasOrdersModule := false
for _, mod := range imports {
switch mod.(type) {
case *users.UsersModule:
hasUsersModule = true
case *orders.OrdersModule:
hasOrdersModule = true
}
}
gt.Expect(hasUsersModule).ToBeTrue()
gt.Expect(hasOrdersModule).ToBeTrue()
}
Testing Module Exports
func TestUsersModule_ExportsUserService(t *testing.T) {
goosetest.SkipUnlessIntegration(t)
gt := goosetest.NewGooseTest(t)
mt := gt.Module(&users.UsersModule{})
exports := mt.TestExports()
// Verify UserService is exported for other modules
hasUserService := false
for _, exp := range exports {
if _, ok := exp.(*users.UserService); ok {
hasUserService = true
break
}
}
gt.Expect(hasUserService).ToBeTrue()
}
Testing Service Integration
Testing Service Dependencies
import (
"testing"
goosetest "github.com/awesome-goose/goose/testing"
"myapp/app/orders"
"myapp/app/users"
"myapp/app/inventory"
)
func TestOrderService_WithRealDependencies(t *testing.T) {
goosetest.SkipUnlessIntegration(t)
gt := goosetest.NewGooseTest(t)
// Set up real (or test) dependencies
gt.WithContainer(func(c *goosetest.TestContainer) {
c.Register(&users.UserService{})
c.Register(&inventory.InventoryService{})
})
orderService := &orders.OrderService{}
st := gt.Service(orderService)
// Test with real service interactions
results := st.Run("CreateOrder", orders.CreateOrderDTO{
UserID: "user-123",
Items: []orders.OrderItem{
{ProductID: "prod-1", Quantity: 2},
},
})
gt.Expect(results[1]).ToBeNil() // no error
order := results[0].(*orders.Order)
gt.Expect(order.UserID).ToEqual("user-123")
}
Testing Cross-Service Communication
func TestUserRegistration_TriggersNotifications(t *testing.T) {
goosetest.SkipUnlessIntegration(t)
gt := goosetest.NewGooseTest(t)
// Set up services with real notification service
notificationService := ¬ifications.NotificationService{}
gt.WithContainer(func(c *goosetest.TestContainer) {
c.Register(notificationService)
})
userService := &users.UserService{}
st := gt.Service(userService)
// Register a new user
results := st.Run("Register", users.RegisterDTO{
Email: "new@example.com",
Name: "New User",
})
gt.Expect(results[1]).ToBeNil()
// Verify notification was sent
gt.Expect(notificationService.SentCount()).ToEqual(1)
}
Testing with Test Database
Database Setup
import (
"os"
"testing"
goosetest "github.com/awesome-goose/goose/testing"
"myapp/app/users"
)
var testDB *sql.DB
func TestMain(m *testing.M) {
// Setup test database
testDB = setupTestDatabase()
code := m.Run()
// Cleanup
testDB.Close()
os.Exit(code)
}
func setupTestDatabase() *sql.DB {
db, err := sql.Open("postgres", os.Getenv("TEST_DATABASE_URL"))
if err != nil {
panic(err)
}
// Run migrations
runMigrations(db)
return db
}
func TestUserRepository_Integration(t *testing.T) {
goosetest.SkipUnlessIntegration(t)
gt := goosetest.NewGooseTest(t)
// Clean up before test
testDB.Exec("TRUNCATE users CASCADE")
repo := &users.UserRepository{DB: testDB}
// Create user
user, err := repo.Create(&users.User{
Email: "test@example.com",
Name: "Test User",
})
gt.Expect(err).ToBeNil()
gt.Expect(user.ID).Not().ToEqual("")
// Find user
found, err := repo.FindByID(user.ID)
gt.Expect(err).ToBeNil()
gt.Expect(found.Email).ToEqual("test@example.com")
// Update user
user.Name = "Updated Name"
err = repo.Update(user)
gt.Expect(err).ToBeNil()
// Delete user
err = repo.Delete(user.ID)
gt.Expect(err).ToBeNil()
// Verify deletion
_, err = repo.FindByID(user.ID)
gt.Expect(err).Not().ToBeNil()
}
Transaction Testing
func TestOrderService_TransactionRollback(t *testing.T) {
goosetest.SkipUnlessIntegration(t)
gt := goosetest.NewGooseTest(t)
testDB.Exec("TRUNCATE orders, inventory CASCADE")
orderService := &orders.OrderService{DB: testDB}
// Try to create order with insufficient inventory
_, err := orderService.CreateOrder(orders.CreateOrderDTO{
UserID: "user-123",
Items: []orders.OrderItem{
{ProductID: "prod-1", Quantity: 1000}, // More than available
},
})
// Should fail
gt.Expect(err).Not().ToBeNil()
// Verify no partial data was saved (transaction rolled back)
var count int
testDB.QueryRow("SELECT COUNT(*) FROM orders WHERE user_id = $1", "user-123").Scan(&count)
gt.Expect(count).ToEqual(0)
}
Testing HTTP Integration
Full Stack HTTP Test
import (
"testing"
goosetest "github.com/awesome-goose/goose/testing"
"myapp/app"
)
func TestFullStackAPI(t *testing.T) {
goosetest.SkipUnlessIntegration(t)
// Create handler with real modules
handler := createHandler(&app.AppModule{})
ht := goosetest.NewHTTPTest(t, handler).WithServer()
defer ht.Close()
// Test full user creation flow
var user map[string]any
ht.POST("/api/users").
WithJSON(map[string]string{
"email": "integration@test.com",
"name": "Integration Test",
}).
Do().
ExpectCreated().
JSON(&user)
userID := user["id"].(string)
ht.T.Expect(userID).Not().ToEqual("")
// Verify user exists in database
ht.GET("/api/users/" + userID).
Do().
ExpectOK()
// Create order for user
ht.POST("/api/orders").
WithJSON(map[string]any{
"user_id": userID,
"items": []map[string]any{
{"product_id": "prod-1", "quantity": 2},
},
}).
Do().
ExpectCreated()
// Get user's orders
var orders []map[string]any
ht.GET("/api/users/" + userID + "/orders").
Do().
ExpectOK().
JSON(&orders)
ht.T.Expect(len(orders)).ToEqual(1)
}
Testing External Services
Mocking External APIs
import (
"net/http"
"net/http/httptest"
"testing"
goosetest "github.com/awesome-goose/goose/testing"
)
func TestPaymentService_Integration(t *testing.T) {
goosetest.SkipUnlessIntegration(t)
gt := goosetest.NewGooseTest(t)
// Create mock payment gateway
mockGateway := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"transaction_id": "txn-123", "status": "success"}`))
}))
defer mockGateway.Close()
// Create payment service with mock gateway URL
paymentService := &payments.PaymentService{
GatewayURL: mockGateway.URL,
}
// Test payment flow
result, err := paymentService.ProcessPayment(payments.PaymentRequest{
Amount: 100.00,
Currency: "USD",
CardID: "card-123",
})
gt.Expect(err).ToBeNil()
gt.Expect(result.TransactionID).ToEqual("txn-123")
gt.Expect(result.Status).ToEqual("success")
}
Best Practices
- Use test tags - Skip integration tests by default with
SkipUnlessIntegration - Clean test data - Reset database state before each test
- Use transactions - Wrap tests in transactions for easy rollback
- Mock external services - Use mock servers for external APIs
- Test realistic scenarios - Test complete user flows, not just happy paths
- Isolate tests - Each test should be independent
Running Integration Tests
# Run only integration tests
TEST_LEVEL=integration go test ./...
# Run with verbose output
TEST_LEVEL=integration go test -v ./...
# Run specific integration test
TEST_LEVEL=integration go test -run TestUserFlow ./tests/...
# Run with timeout (integration tests may be slower)
TEST_LEVEL=integration go test -timeout 5m ./...
Next Steps
- Unit Testing - Test individual components
- HTTP Testing - Test API endpoints
- E2E Testing - Test full user flows