Skip to main content

Overview

The Auth0 Go SDK uses HTTP recordings for testing, allowing you to:
  • Test without hitting live Auth0 APIs
  • Create fast, reliable, and repeatable tests
  • Avoid rate limits and network dependencies
  • Record real API interactions for playback
The SDK uses WireMock for HTTP recording and playback.

Test Environment Setup

Prerequisites

# Install Go 1.24 or newer
go version

# Download dependencies
go mod download

# Or use vendor folder
make deps

Running Tests with HTTP Recordings

The SDK includes HTTP recordings for all tests:
# Run all tests with recordings (recommended for development)
make test

# Run specific test with recordings
make test FILTER="TestResourceServer_Read"
This automatically:
  1. Starts a WireMock Docker container
  2. Loads HTTP recordings from test/data/recordings/
  3. Runs tests against the mock server
  4. Stops the container when done

Running Tests Against Real Auth0 Tenant

For end-to-end testing against a live Auth0 tenant:
# Run all E2E tests
make test-e2e

# Run specific E2E test
make test-e2e FILTER="TestResourceServer_Read"
E2E tests hit real Auth0 APIs and may:
  • Count against your API rate limits
  • Create/modify/delete resources in your tenant
  • Take longer to execute (20-minute timeout)
Use E2E tests sparingly for validation before releases.

Environment Variables for E2E Tests

Create a .env file in the project root:
AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_CLIENT_ID=your_management_m2m_client_id
AUTH0_CLIENT_SECRET=your_management_m2m_client_secret
AUTH0_AUTH_CLIENT_ID=your_authentication_m2m_client_id
AUTH0_AUTH_CLIENT_SECRET=your_authentication_m2m_client_secret
AUTH0_DEBUG=true  # Optional: enables debug logging
You need two M2M applications:
  1. Management API: For management API operations (clients, users, etc.)
  2. Authentication API: For authentication API operations (tokens, user info, etc.)
Both should have appropriate scopes for the operations you’re testing.

Recording New HTTP Interactions

When adding new tests or updating existing ones:
# Record new HTTP interactions
make test-record

# Record specific test
make test-record FILTER="TestResourceServer_Read"
This will:
  1. Run tests against your real Auth0 tenant
  2. Record all HTTP requests and responses
  3. Save recordings to test/data/recordings/
Important: When regenerating recordings for an existing test, delete the old recording file first:
rm test/data/recordings/TestResourceServer_Read.json
make test-record FILTER="TestResourceServer_Read"

Writing Tests with the SDK

Basic Test Structure

package myapp_test

import (
    "context"
    "testing"

    "github.com/auth0/go-auth0/v2/management"
    "github.com/auth0/go-auth0/v2/management/client"
    "github.com/auth0/go-auth0/v2/management/option"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestClientOperations(t *testing.T) {
    // Set up management client
    mgmt, err := client.New(
        "your-tenant.auth0.com",
        option.WithClientCredentials(
            context.Background(),
            "YOUR_CLIENT_ID",
            "YOUR_CLIENT_SECRET",
        ),
    )
    require.NoError(t, err)

    ctx := context.Background()

    // Test creating a client
    createRequest := &management.CreateClientRequestContent{
        Name:    "Test Client",
        AppType: &management.ClientAppTypeEnumSpa,
    }

    client, err := mgmt.Clients.Create(ctx, createRequest)
    require.NoError(t, err)
    assert.NotNil(t, client.GetClientID())
    assert.Equal(t, "Test Client", *client.GetName())

    // Clean up
    defer mgmt.Clients.Delete(ctx, *client.GetClientID())

    // Test reading the client
    retrieved, err := mgmt.Clients.Get(ctx, *client.GetClientID())
    require.NoError(t, err)
    assert.Equal(t, *client.GetClientID(), *retrieved.GetClientID())
}

Testing with Subtests

func TestUserManagement(t *testing.T) {
    mgmt := setupTestClient(t)
    ctx := context.Background()

    t.Run("create user", func(t *testing.T) {
        createRequest := &management.CreateUserRequestContent{
            Email:      "[email protected]",
            Connection: "Username-Password-Authentication",
            Password:   management.String("Test123!"),
        }

        user, err := mgmt.Users.Create(ctx, createRequest)
        require.NoError(t, err)
        assert.NotEmpty(t, *user.GetUserID())

        // Store for other subtests
        t.Cleanup(func() {
            mgmt.Users.Delete(ctx, *user.GetUserID())
        })
    })

    t.Run("list users", func(t *testing.T) {
        usersPage, err := mgmt.Users.List(ctx, &management.ListUsersRequestParameters{
            PerPage: management.Int(10),
        })
        require.NoError(t, err)
        assert.NotEmpty(t, usersPage.Results)
    })

    t.Run("update user", func(t *testing.T) {
        // Test user updates
    })
}

Testing Pagination

func TestPagination(t *testing.T) {
    mgmt := setupTestClient(t)
    ctx := context.Background()

    // Create test data
    for i := 0; i < 30; i++ {
        mgmt.Clients.Create(ctx, &management.CreateClientRequestContent{
            Name: fmt.Sprintf("Test Client %d", i),
        })
    }

    t.Run("iterate all pages", func(t *testing.T) {
        listRequest := &management.ListClientsRequestParameters{
            PerPage: management.Int(10),
        }

        clientsPage, err := mgmt.Clients.List(ctx, listRequest)
        require.NoError(t, err)

        totalClients := 0
        iterator := clientsPage.Iterator()
        for iterator.Next(ctx) {
            totalClients++
        }
        require.NoError(t, iterator.Err())
        assert.GreaterOrEqual(t, totalClients, 30)
    })

    t.Run("manual pagination", func(t *testing.T) {
        page := 0
        totalClients := 0

        for {
            clientsPage, err := mgmt.Clients.List(ctx, &management.ListClientsRequestParameters{
                Page:    management.Int(page),
                PerPage: management.Int(10),
            })
            require.NoError(t, err)

            totalClients += len(clientsPage.Results)

            nextPage, err := clientsPage.GetNextPage(ctx)
            if errors.Is(err, core.ErrNoPages) {
                break
            }
            require.NoError(t, err)

            clientsPage = nextPage
            page++
        }

        assert.GreaterOrEqual(t, totalClients, 30)
    })
}

Testing Error Conditions

func TestErrorHandling(t *testing.T) {
    mgmt := setupTestClient(t)
    ctx := context.Background()

    t.Run("not found error", func(t *testing.T) {
        _, err := mgmt.Clients.Get(ctx, "non_existent_client_id")
        assert.Error(t, err)
        // Check for specific error type if needed
    })

    t.Run("validation error", func(t *testing.T) {
        createRequest := &management.CreateClientRequestContent{
            Name: "", // Empty name should fail
        }

        _, err := mgmt.Clients.Create(ctx, createRequest)
        assert.Error(t, err)
    })

    t.Run("context cancellation", func(t *testing.T) {
        cancelCtx, cancel := context.WithCancel(ctx)
        cancel() // Cancel immediately

        _, err := mgmt.Clients.List(cancelCtx, nil)
        assert.ErrorIs(t, err, context.Canceled)
    })

    t.Run("context timeout", func(t *testing.T) {
        timeoutCtx, cancel := context.WithTimeout(ctx, 1*time.Nanosecond)
        defer cancel()

        time.Sleep(10 * time.Millisecond) // Ensure timeout

        _, err := mgmt.Clients.List(timeoutCtx, nil)
        assert.ErrorIs(t, err, context.DeadlineExceeded)
    })
}

Using WireMock for Custom Tests

You can run WireMock independently for custom test scenarios:
# Set a custom WireMock port
export WIREMOCK_PORT=8080

# Start WireMock container
docker compose -f wiremock/docker-compose.test.yml up -d

# Run tests
AUTH0_HTTP_RECORDINGS=on \
AUTH0_DOMAIN=go-auth0-dev.eu.auth0.com \
WIREMOCK_PORT=8080 \
WIREMOCK_URL=http://localhost:8080 \
go test ./...

# Stop WireMock when done
docker compose -f wiremock/docker-compose.test.yml down -v

Test Helpers

Create reusable test helpers:
package testutil

import (
    "context"
    "os"
    "testing"

    "github.com/auth0/go-auth0/v2/management"
    "github.com/auth0/go-auth0/v2/management/client"
    "github.com/auth0/go-auth0/v2/management/option"
)

// SetupTestClient creates a management client for testing
func SetupTestClient(t *testing.T) *management.Management {
    t.Helper()

    domain := os.Getenv("AUTH0_DOMAIN")
    clientID := os.Getenv("AUTH0_CLIENT_ID")
    clientSecret := os.Getenv("AUTH0_CLIENT_SECRET")

    if domain == "" || clientID == "" || clientSecret == "" {
        t.Skip("Skipping test: AUTH0 credentials not configured")
    }

    mgmt, err := client.New(
        domain,
        option.WithClientCredentials(
            context.Background(),
            clientID,
            clientSecret,
        ),
    )
    if err != nil {
        t.Fatalf("Failed to create management client: %v", err)
    }

    return mgmt
}

// CreateTestClient creates a temporary test client and returns cleanup function
func CreateTestClient(t *testing.T, mgmt *management.Management) (*management.Client, func()) {
    t.Helper()

    ctx := context.Background()
    createRequest := &management.CreateClientRequestContent{
        Name:    "Test Client - " + t.Name(),
        AppType: &management.ClientAppTypeEnumSpa,
    }

    client, err := mgmt.Clients.Create(ctx, createRequest)
    if err != nil {
        t.Fatalf("Failed to create test client: %v", err)
    }

    cleanup := func() {
        mgmt.Clients.Delete(ctx, *client.GetClientID())
    }

    return client, cleanup
}

// CreateTestUser creates a temporary test user and returns cleanup function
func CreateTestUser(t *testing.T, mgmt *management.Management, email string) (*management.User, func()) {
    t.Helper()

    ctx := context.Background()
    createRequest := &management.CreateUserRequestContent{
        Email:      email,
        Connection: "Username-Password-Authentication",
        Password:   management.String("Test123!@#"),
    }

    user, err := mgmt.Users.Create(ctx, createRequest)
    if err != nil {
        t.Fatalf("Failed to create test user: %v", err)
    }

    cleanup := func() {
        mgmt.Users.Delete(ctx, *user.GetUserID())
    }

    return user, cleanup
}
Usage:
func TestWithHelpers(t *testing.T) {
    mgmt := testutil.SetupTestClient(t)
    ctx := context.Background()

    // Create test client with automatic cleanup
    client, cleanup := testutil.CreateTestClient(t, mgmt)
    defer cleanup()

    // Test with the client
    retrieved, err := mgmt.Clients.Get(ctx, *client.GetClientID())
    require.NoError(t, err)
    assert.Equal(t, *client.GetName(), *retrieved.GetName())
}

Best Practices

Use Recordings for CI

Always use HTTP recordings in CI/CD pipelines to avoid rate limits and ensure consistent, fast test execution.

Clean Up Resources

Always clean up test resources using defer or t.Cleanup() to avoid polluting your test tenant.

Use Unique Names

Include test name or timestamp in resource names to avoid conflicts:
Name: "Test Client - " + t.Name()

Skip When Needed

Skip tests when credentials aren’t available:
if os.Getenv("AUTH0_DOMAIN") == "" {
    t.Skip("Skipping: no credentials")
}

Makefile Test Targets

The SDK provides several make targets for testing:
make test          # Run tests with HTTP recordings (default)
make test-record   # Record new HTTP interactions
make test-e2e      # Run tests against real Auth0 tenant
make lint          # Run linter
make check-vuln    # Check for vulnerabilities

Test Configuration

The test commands use these settings:
  • Coverage mode: atomic (safe for parallel tests)
  • Coverage output: coverage.out
  • E2E timeout: 20 minutes
  • Recordings directory: test/data/recordings/

Debugging Tests

Enable debug mode for verbose output:
# Enable debug mode for E2E tests
AUTH0_DEBUG=true make test-e2e

# Or set in .env file
echo "AUTH0_DEBUG=true" >> .env
For recording mode:
# Record with debug output
AUTH0_DEBUG=true make test-record FILTER="TestYourTest"

Common Testing Patterns

Table-Driven Tests

func TestClientValidation(t *testing.T) {
    tests := []struct {
        name    string
        request *management.CreateClientRequestContent
        wantErr bool
    }{
        {
            name: "valid spa client",
            request: &management.CreateClientRequestContent{
                Name:    "Valid SPA",
                AppType: &management.ClientAppTypeEnumSpa,
            },
            wantErr: false,
        },
        {
            name: "empty name",
            request: &management.CreateClientRequestContent{
                Name: "",
            },
            wantErr: true,
        },
    }

    mgmt := setupTestClient(t)
    ctx := context.Background()

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            client, err := mgmt.Clients.Create(ctx, tt.request)
            if tt.wantErr {
                assert.Error(t, err)
            } else {
                assert.NoError(t, err)
                defer mgmt.Clients.Delete(ctx, *client.GetClientID())
            }
        })
    }
}

Build docs developers (and LLMs) love