Skip to content

Testing

Unit Tests (envtest)

make test

Uses controller-runtime’s envtest — a real API server + etcd, no kubelet. Coverage report lands in cover.out.

Test Locations

PathWhat it tests
internal/controller/*_test.goController reconciliation logic
internal/pacing/*_test.goPacing engine constraints
internal/podbuilder/*_test.goPod construction correctness
internal/discovery/*_test.goSource implementations

E2E Tests (Chainsaw)

make test-e2e

Requires a running kind cluster with the operator deployed (Tilt handles this). Tests live in test/e2e/ and use Kyverno Chainsaw.

Each test scenario is a directory with chainsaw-test.yaml defining steps:

  1. Apply a resource
  2. Assert expected state (status, child resources, events)
  3. Cleanup

Writing Tests

Table-driven (preferred)

func TestSomething(t *testing.T) {
    tests := []struct {
        name string
        // inputs
        // expected outputs
    }{
        {name: "happy path", ...},
        {name: "error case", ...},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            // arrange, act, assert
        })
    }
}

Controller tests (envtest)

var k8sClient client.Client
var testEnv *envtest.Environment
// Setup in TestMain or BeforeSuite

Create resources with the real client, trigger reconciliation, assert status changes.

Discovery tests (mock HTTP)

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // Return mock Prometheus/Registry response
}))
defer srv.Close()

source := &PrometheusSource{Endpoint: srv.URL, ...}
results, err := source.Fetch(ctx)

Adding a New Test

  1. Create *_test.go next to the code being tested
  2. Use table-driven format with descriptive case names
  3. For controllers: create the CRD resource, reconcile, assert status
  4. For discovery: mock the HTTP endpoint, call Fetch, assert results
  5. Run make test to validate
Last updated on