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
| Variable | Description | Example |
|---|---|---|
| APPGRAM_URL | Base URL of AppGram server | http://localhost:3001 |
| STATUS_PAGE_ID | Public status page ID | aaa94712-bb41... |
| TIMEOUT | Request timeout in seconds | 10 |
| ENVIRONMENT | Environment name | development |
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