Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Testing

The Keycloak Operator has two levels of testing:

  1. Unit Tests: Fast, isolated tests using envtest
  2. End-to-End Tests: Full cluster tests against Kind with Keycloak

Unit Tests

Run unit tests with:

make test

Unit tests use the controller-runtime’s envtest package to provide a lightweight Kubernetes API server. These don’t require a real Keycloak instance.

Coverage

make test
go tool cover -html=cover.out

End-to-End Tests

E2E tests run against a full Kind cluster with the operator and Keycloak deployed.

Understanding E2E Test Network Topology

E2E tests involve two different network perspectives:

  1. Operator’s perspective (inside the cluster): The operator connects to Keycloak using the in-cluster service URL (e.g., http://keycloak.keycloak.svc.cluster.local)
  2. Test’s perspective (your local machine): When running tests locally, you need port-forwarding to access Keycloak directly for certain tests (drift detection, cleanup verification)
┌─────────────────────────────────────────────────────────┐
│                     Kind Cluster                         │
│  ┌─────────────┐      ┌──────────────────┐             │
│  │  Operator   │──────│     Keycloak     │             │
│  │             │      │  (port 80/8080)  │             │
│  └─────────────┘      └────────┬─────────┘             │
│                                │                        │
└────────────────────────────────┼────────────────────────┘
                                 │ port-forward
                                 ▼
                    ┌────────────────────────┐
                    │   localhost:8080       │
                    │   (your machine)       │
                    └────────────────────────┘

Running E2E Tests

Recommended approach (fully automated):

# Full setup: creates cluster, deploys operator and Keycloak, runs tests
make kind-all
make kind-test-e2e

The kind-test-e2e target runs ./hack/setup-kind.sh test-e2e, which:

  1. Sets up port-forwarding to Keycloak automatically
  2. Configures environment variables
  3. Runs the e2e test suite with a 30-minute timeout

Manual setup (for development):

# 1. Ensure cluster and operator are running
make kind-all

# 2. In a separate terminal, start port-forward
kubectl port-forward -n keycloak svc/keycloak 8080:80

# 3. Run tests with required environment variables
export USE_EXISTING_CLUSTER=true
export KEYCLOAK_URL="http://localhost:8080"                      # For test's direct Keycloak access
export KEYCLOAK_INTERNAL_URL="http://keycloak.keycloak.svc.cluster.local"  # For operator (inside cluster)
go test -v -timeout 30m ./test/e2e/...

Note: Tests that require direct Keycloak access (drift detection, cleanup verification) will be automatically skipped if port-forward is not available. This allows running basic E2E tests without port-forwarding, while advanced tests require it.

E2E Test Configuration

VariableDescriptionDefault
USE_EXISTING_CLUSTERSet to true to use current kubeconfigfalse
KEYCLOAK_INSTANCE_NAMEName of existing KeycloakInstance to use(creates new)
KEYCLOAK_INSTANCE_NAMESPACENamespace of existing instancekeycloak-operator-e2e
OPERATOR_NAMESPACENamespace where operator is deployedkeycloak-operator
KEYCLOAK_URLURL for test’s direct Keycloak access (via port-forward)http://localhost:8080
KEYCLOAK_INTERNAL_URLURL operator uses to connect (in-cluster)http://keycloak.keycloak.svc.cluster.local
TEST_NAMESPACENamespace for test resourceskeycloak-operator-e2e
KEEP_TEST_NAMESPACEDon’t delete namespace after testsfalse

Test Categories

CategoryRequires Port-ForwardDescription
Basic CRUDNoCreate, update, delete resources via Kubernetes API
Status verificationNoVerify .status.ready and conditions
Drift detectionYesTests that modify Keycloak directly and verify reconciliation
Cleanup verificationYesTests that verify resources are deleted from Keycloak
Edge casesMixedSome require direct access, some don’t

Writing Tests

Unit Test Example

func TestRealmController_Reconcile(t *testing.T) {
    // Setup
    scheme := runtime.NewScheme()
    _ = keycloakv1beta1.AddToScheme(scheme)
    
    realm := &keycloakv1beta1.KeycloakRealm{
        ObjectMeta: metav1.ObjectMeta{
            Name:      "test-realm",
            Namespace: "default",
        },
        Spec: keycloakv1beta1.KeycloakRealmSpec{
            InstanceRef: "test-instance",
        },
    }
    
    client := fake.NewClientBuilder().
        WithScheme(scheme).
        WithObjects(realm).
        Build()
    
    // Test reconciliation...
}

E2E Test Example

func TestKeycloakRealmE2E(t *testing.T) {
    skipIfNoCluster(t)
    
    realm := &keycloakv1beta1.KeycloakRealm{
        ObjectMeta: metav1.ObjectMeta{
            Name:      "e2e-realm",
            Namespace: testNamespace,
        },
        Spec: keycloakv1beta1.KeycloakRealmSpec{
            InstanceRef: instanceName,
            Definition: rawJSON(`{"realm": "e2e-realm", "enabled": true}`),
        },
    }
    
    require.NoError(t, k8sClient.Create(ctx, realm))
    t.Cleanup(func() {
        k8sClient.Delete(ctx, realm)
    })
    
    // Wait for ready
    err := wait.PollUntilContextTimeout(ctx, interval, timeout, true, 
        func(ctx context.Context) (bool, error) {
            updated := &keycloakv1beta1.KeycloakRealm{}
            if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(realm), updated); err != nil {
                return false, nil
            }
            return updated.Status.Ready, nil
        })
    require.NoError(t, err)
}

// Example: Test requiring direct Keycloak access (drift detection)
func TestDriftDetection(t *testing.T) {
    skipIfNoCluster(t)
    skipIfNoKeycloakAccess(t)  // Skips if port-forward not available
    
    // ... test that modifies Keycloak directly ...
}

CI/CD

Tests run automatically in GitHub Actions:

  • Unit tests on every PR
  • E2E tests on merge to main

Test Utilities

Common test utilities are in test/e2e/suite_test.go:

  • skipIfNoCluster(t): Skip test if USE_EXISTING_CLUSTER is not set
  • skipIfNoKeycloakAccess(t): Skip test if direct Keycloak access (port-forward) is unavailable
  • getInternalKeycloakClient(t): Create authenticated Keycloak client for direct API access
  • rawJSON(s string): Create runtime.RawExtension from JSON string
  • canConnectToKeycloak(): Check if direct Keycloak connection is available