envtest
```html
Envtest: The Complete Playbook for Testing Kubernetes Controllers — Key Takeaways, Checklist & FAQs
Envtest: The Complete Playbook for Testing Kubernetes Controllers
Key Takeaways
- Envtest is the de facto standard for integration-testing Kubernetes controllers and custom APIs without needing a live cluster.
- It boots a real
etcdandapiserverin-process, giving you production-faithful test semantics at near-unit-test speed. - Pair envtest with Kubebuilder or controller-runtime's
schemeregistration for zero-boilerplate setup. - Common failure modes — missing CRD manifests, stale binary assets, race conditions in async controllers — are all preventable with a disciplined checklist.
- For best ROI, reserve envtest for reconciler-level and CRD-level tests; use unit tests for pure logic and e2e tests for deployment-logic verification.
What Is Envtest?
Definition
envtest (short for "environment test") is a testing utility from the k8s.io/client-go ecosystem that spins up a minimal, in-process Kubernetes control plane — a real etcd instance and a real apiserver — during test execution. It lets Go developers exercise their controller logic against actual Kubernetes API machinery, including CRD discovery, scheme resolution, and the rest mapper, without provisioning a real cluster.
Why Envtest Matters
- Faithfulness: Tests run against the same REST machinery that production controllers hit, catching regressions in serialization, validation, and CRD resolution that unit tests miss.
- Speed: Startup is measured in seconds, not minutes, making it viable for CI pipelines on every pull request.
- Isolation: Each test gets its own
etcdinstance, eliminating flaky cross-test state leakage. - No cluster required: Works on developer laptops, CI runners, and air-gapped environments.
Envtest Setup: Step-by-Step Playbook
Step 1 — Install the Envtest Binaries
Envtest ships as a standalone binary bundle (statically compiled etcd and apiserver). Download them once per Go module version.
- Install the
kubebuilderCLI:go install sigs.k8s.io/kubebuilder/cmd/kubebuilder@latest - Download the environment binaries matching your
k8s.io/*dependency version:kubebuilder create apitriggers automatic download, or run manually:kubebuilder assets download - Verify the binaries landed in the envtest data directory:
~/.local/share/kubebuilder-envtest/(or$KUBEBUILDER_ASSETStarget)
Step 2 — Configure Your Go Module
- Ensure
sigs.k8s.io/controller-runtimeis at the same minor version as yourk8s.iodependencies. - Add
k8s.io/api,k8s.io/apimachinery, andk8s.io/client-gotogo.mod. - Pin the
k8s.ioversion — envtest binaries must exactly match the major.minor patch of the client libraries.
Step 3 — Register Your Scheme
- Create a shared
runtime.Schemethat registers every custom type your controller manages. - Call
scheme.AddToScheme(scheme)for each group/version package. - Validate the scheme is complete:
scheme.AllKnownTypes()should include every CRD type referenced in your tests.
Step 4 — Set Up the Envtest Control Plane
- Set the
KUBEBUILDER_ASSETSenvironment variable before any test runs. - In
_test.go, useenvtest.Environmentto launch the control plane:
var env *envtest.Environment
func TestMain(m *testing.M) {
cfg, err := envtest.Run(envtest.RunInput{
CRDDirectoryPaths: []string{"config/crd/bases"},
ErrorIfCRDPathMissing: true,
BinAssetsDir: os.Getenv("KUBEBUILDER_ASSETS"),
})
// … wire cfg into your manager and run tests …
}
Step 5 — Write Your First Controller Test
- Create the controller instance, injecting a client built from the envtest config.
- Use
testenv.Client(anclient.Clientbacked by the in-process apiserver) for all CRUD operations. - Sleep or poll for reconciliation results using
gomega.Eventually. - Clean up created objects with
deferor withinAfterEach.
Envtest Best-Practices Checklist
Before every envtest PR, run through this checklist to ensure your tests are reliable, fast, and maintainable.
| # | Checklist Item | Priority | Status | Notes |
|---|---|---|---|---|
| 1 | Binaries match k8s.io dependency version exactly |
Critical | ☐ | Run kubebuilder assets download after any go get bump. |
| 2 | CRD manifests are valid YAML with correct apiVersion |
Critical | ☐ | Use kubectl apply --dry-run=server -f to validate. |
| 3 | KUBEBUILDER_ASSETS is set in CI configuration |
Critical | ☐ | Add to .github/workflows/ or Jenkinsfile. |
| 4 | Each test creates its own namespace (or uses t.Parallel()-safe isolation) |
High | ☐ | Prevents resource name collisions across parallel tests. |
| 5 | All created objects are cleaned up in defer or AfterEach |
High | ☐ | Orphaned objects leak into subsequent test runs. |
| 6 | Reconciliation waits use Eventually with a reasonable timeout (≥10 s) |
High | ☐ | Short timeouts cause flaky failures under CI load. |
| 7 | Controller reconciler is stubbed or mocked where appropriate (IO, external APIs) | Medium | ☐ | Keep envtest focused on K8s-API interaction, not external behavior. |
| 8 | Tests run in parallel (t.Parallel()) where state is isolated |
Medium |