1. My Awesome App
  2. Help
  3. Go API Testing Best Practices for AppGram

Go API Testing Best Practices for AppGram

Comprehensive guide to testing AppGram APIs with Go, including best practices, error handling, CI/CD integration, and advanced testing scenarios.

Introduction

This guide covers best practices for testing AppGram APIs using Go, extending concepts from the test_status_overview.go script to create robust test suites.

Environment Setup

1. Configuration Management

// config.go
package main

import (
    "os"
    "strconv"
)

type Config struct {
    BaseURL    string
    Timeout    time.Duration
    StatusPageID string
}

func LoadConfig() *Config {
    return &Config{
        BaseURL:     getEnv("APPGRAM_URL", "http://127.0.0.1:3001"),
        Timeout:     time.Duration(getEnvInt("TIMEOUT", 10)) * time.Second,
        StatusPageID: getEnv("STATUS_PAGE_ID", "aaa94712-bb41-4536-a674-1df42bc06068"),
    }
}

func getEnv(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

Enhanced Testing Script

2. Structured Error Handling

// test_enhanced.go
package main

import (
    "context"
    "fmt"
    "net/http"
    "time"
)

type StatusTester struct {
    client *http.Client
    config *Config
}

func NewStatusTester(config *Config) *StatusTester {
    return &StatusTester{
        client: &http.Client{
            Timeout: config.Timeout,
        },
        config: config,
    }
}

func (st *StatusTester) TestOverview() error {
    ctx, cancel := context.WithTimeout(context.Background(), st.config.Timeout)
    defer cancel()
    
    url := fmt.Sprintf("%s/api/v1/status-pages/public/%s/status/overview",
        st.config.BaseURL, st.config.StatusPageID)
    
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return fmt.Errorf("creating request: %w", err)
    }
    
    resp, err := st.client.Do(req)
    if err != nil {
        return fmt.Errorf("making request: %w", err)
    }
    defer resp.Body.Close()
    
    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("unexpected status: %d", resp.StatusCode)
    }
    
    return nil
}

Advanced Testing Scenarios

3. Service Validation

type Service struct {
    ID     string `json:"id"`
    Name   string `json:"name"`
    Status string `json:"status"`
    Uptime float64 `json:"uptime"`
}

type StatusResponse struct {
    Data struct {
        Services []Service `json:"services"`
    } `json:"data"`
}

func (st *StatusTester) ValidateServices() ([]Service, error) {
    var response StatusResponse
    // ... make request and decode JSON ...
    
    if len(response.Data.Services) == 0 {
        return nil, fmt.Errorf("no services found")
    }
    
    for _, service := range response.Data.Services {
        if service.Status == "" {
            return nil, fmt.Errorf("service %s has empty status", service.Name)
        }
    }
    
    return response.Data.Services, nil
}

Testing Framework Integration

4. Using Go's Testing Package

// status_test.go
package main

import (
    "testing"
    "time"
)

func TestStatusPageOverview(t *testing.T) {
    config := &Config{
        BaseURL:     "http://127.0.0.1:3001",
        Timeout:     5 * time.Second,
        StatusPageID: "your-status-page-id",
    }
    
    tester := NewStatusTester(config)
    
    err := tester.TestOverview()
    if err != nil {
        t.Fatalf("Status page test failed: %v", err)
    }
}

func TestServiceValidation(t *testing.T) {
    config := LoadConfig()
    tester := NewStatusTester(config)
    
    services, err := tester.ValidateServices()
    if err != nil {
        t.Fatalf("Service validation failed: %v", err)
    }
    
    for _, service := range services {
        if service.Uptime < 99.0 {
            t.Errorf("Service %s has low uptime: %.2f%%", service.Name, service.Uptime)
        }
    }
}

Performance Testing

5. Load Testing

// load_test.go
package main

import (
    "sync"
    "testing"
    "time"
)

func TestStatusPageLoad(t *testing.T) {
    config := LoadConfig()
    tester := NewStatusTester(config)
    
    const requests = 100
    const concurrency = 10
    
    var wg sync.WaitGroup
    wg.Add(requests)
    
    errors := make(chan error, requests)
    
    for i := 0; i < concurrency; i++ {
        go func() {
            for j := 0; j < requests/concurrency; j++ {
                defer wg.Done()
                if err := tester.TestOverview(); err != nil {
                    errors <- err
                }
            }
        }()
    }
    
    wg.Wait()
    close(errors)
    
    errCount := 0
    for err := range errors {
        errCount++
        t.Logf("Request failed: %v", err)
    }
    
    if errCount > 0 {
        t.Errorf("%d out of %d requests failed", errCount, requests)
    }
}

CI/CD Integration

6. GitHub Actions Workflow

# .github/workflows/status-test.yml
name: Status Page Tests

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Go
      uses: actions/setup-go@v3
      with:
        go-version: 1.19
    
    - name: Install dependencies
      run: go mod download
    
    - name: Run status page tests
      env:
        APPGRAM_URL: ${{ secrets.APPGRAM_URL }}
        STATUS_PAGE_ID: ${{ secrets.STATUS_PAGE_ID }}
      run: go test -v ./...

Environment Variables

7. Required Variables

VariableDescriptionExample
APPGRAM_URLBase URL of AppGram serverhttp://localhost:3001
STATUS_PAGE_IDPublic status page IDaaa94712-bb41...
TIMEOUTRequest timeout in seconds10
ENVIRONMENTEnvironment namedevelopment

Monitoring and Alerts

8. Health Check Endpoint

// health_check.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

func healthHandler(w http.ResponseWriter, r *http.Request) {
    config := LoadConfig()
    tester := NewStatusTester(config)
    
    start := time.Now()
    err := tester.TestOverview()
    duration := time.Since(start)
    
    if err != nil {
        http.Error(w, fmt.Sprintf("Health check failed: %v", err), http.StatusServiceUnavailable)
        return
    }
    
    response := map[string]interface{}{
        "status":   "healthy",
        "duration": duration.Milliseconds(),
        "timestamp": time.Now().Unix(),
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(response)
}

func main() {
    http.HandleFunc("/health", healthHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Troubleshooting

9. Common Issues and Solutions

Connection Timeouts

  • Check server status: curl http://localhost:3001/health
  • Increase timeout value
  • Verify network connectivity

Invalid JSON Response

  • Check API version compatibility
  • Validate response with JSON schema
  • Handle partial responses gracefully

Missing Services

  • Verify status page configuration
  • Check service visibility settings
  • Confirm service monitoring is active

Next Steps

  • Implement retry logic for transient failures
  • Add comprehensive logging
  • Create monitoring dashboards
  • Set up alerting for service degradation