Testing guidelines for the Managed Node Metadata.
- Ginkgo v2: BDD testing framework
- Gomega: Matchers and assertions
- GoMock: Interface mocking
- envtest: Kubernetes API server for controller testing
# Run all tests
make go-test
# Run tests with Ginkgo runner
ginkgo -r ./...
# Run specific package
go test ./controllers/
# Verbose output
ginkgo -v ./...
# Run focused test
ginkgo -focus="NetworkPolicy" ./controllers/
# Container-based (CI parity)
boilerplate/_lib/container-make go-testEach package with tests includes:
*_suite_test.go: Ginkgo test suite setup*_test.go: Actual test cases
Example:
package mypackage_test
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("MyFeature", func() {
Context("when condition X", func() {
It("should do Y", func() {
result := MyFunction()
Expect(result).To(Equal(expected))
})
})
})cd pkg/newpackage
ginkgo bootstrap # Creates suite
ginkgo generate myfile.go # Creates test fileUse GoMock for external dependencies:
//go:generate mockgen -destination=mocks/mock_client.go -package=mocks sigs.k8s.io/controller-runtime/pkg/client ClientRegenerate all mocks:
boilerplate/_lib/container-make generateWhy container-make?
- Ensures same mockgen version as CI
- Prevents version drift in generated code
- Test individual functions and methods
- Mock external dependencies (Kubernetes client, HTTP calls)
- Fast execution (<1s per package)
- Located alongside source code
- Test reconciliation logic
- Use envtest for simulated Kubernetes API
- Test custom resource lifecycle
- Located in
controllers/*/
- Full operator deployment
- Real cluster interaction
- Located in
test/e2e/ - Run in CI via Tekton
When AI agents modify code:
Minimal validation:
# After changing controllers/
go test ./controllers/Full validation before commit:
make go-testIf tests fail:
- Read test output carefully
- Fix the underlying issue (don't skip tests)
- Rerun to confirm fix
- Regenerate mocks if interface changed:
boilerplate/_lib/container-make generate
```go
It("should reconcile resource", func() {
// Create custom resource
resource := &v1alpha1.CustomResource{...}
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
// Trigger reconciliation
_, err := reconciler.Reconcile(ctx, req)
Expect(err).NotTo(HaveOccurred())
// Verify reconciliation result
Expect(k8sClient.Get(ctx, resourceKey, resource)).To(Succeed())
Expect(resource.Status.Conditions).ToNot(BeEmpty())
})
### Testing Error Conditions
```go
It("should return error when resource not found", func() {
_, err := reconciler.Reconcile(ctx, reqForNonExistent)
Expect(err).To(HaveOccurred())
})
// Equality
Expect(result).To(Equal(expected))
// Nil checks
Expect(err).NotTo(HaveOccurred())
Expect(obj).To(BeNil())
// Collections
Expect(slice).To(ContainElement("item"))
Expect(slice).To(HaveLen(3))
Expect(slice).To(BeEmpty())
// Booleans
Expect(condition).To(BeTrue())
Expect(condition).To(BeFalse())
// Eventually (async)
Eventually(func() bool {
return checkCondition()
}).Should(BeTrue())Generate coverage report:
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.htmlNote: Aim for meaningful coverage, not arbitrary percentages.
- Test critical paths and error handling
- Don't test generated code or trivial getters/setters
# Verbose Ginkgo output
ginkgo -v ./...
# Print statements in tests
fmt.Fprintf(GinkgoWriter, "Debug: %v\n", value)
# Skip flaky tests temporarily
ginkgo -skip="FlakyTest" ./...
# Run single test
ginkgo -focus="exact test name" ./...Tests run in Tekton pipeline with:
- Fresh environment
- No cached dependencies
- Strict timeout limits
Local CI parity:
boilerplate/_lib/container-make go-testTarget timings:
- Unit tests: <5s per package
- Controller tests: <15s per controller
- Full suite: <2min
If tests are slow:
- Check for unnecessary sleeps
- Use
Eventuallywith shorter intervals - Mock external calls
- Avoid creating unnecessary Kubernetes resources
Mock not found:
# Regenerate mocks
boilerplate/_lib/container-make generateenvtest not installed:
make setup-envtestTest passes locally, fails in CI:
# Run in container environment
boilerplate/_lib/container-make go-test
# Check for:
# - Time-dependent tests
# - Environment-specific assumptions
# - File path dependenciesFlaky tests:
- Use
Eventuallyinstead ofExpectfor async operations - Avoid hardcoded delays
- Ensure test isolation (clean up resources)
Tests run automatically in pre-commit when Go files change:
- id: go-test
entry: make go-test
files: '\.go$'This is NOT in current pre-commit config (too slow for pre-commit).
Run manually before pushing: make go-test