testutil

package
v0.10.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 9, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Overview

Package testutil provides test utilities for Kubernetes operators. The main support is around envtest and resource watching logic, where you can use the following code to wait for the matching resource to appear.

Example:

watcher.SetCmpOpts(testutil.CompareSpecOnly()...)
expectedSts := &appsv1.StatefulSet{
    Spec: appsv1.StatefulSetSpec{Replicas: ptr.To(int32(3))},
}
expectedSvc := &corev1.Service{...}

err := watcher.WaitForMatch(expectedSts, expectedSvc)
if err != nil {
    t.Errorf("Resources never reached expected state: %v", err)
}

This utility allows resource matching logic to be fully declarative, and while integration tests are more expensive to run in terms of time spent, this makes sure that there is no unnecessary sleep logic, and all the tests can be run in an event driven fashion.

Package testutil provides testing utilities for controller tests.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInjected        = fmt.Errorf("injected test error")
	ErrNetworkTimeout  = fmt.Errorf("network timeout")
	ErrPermissionError = fmt.Errorf("permission denied")
)

Common errors for testing

Functions

func AlwaysFail

func AlwaysFail(err error) func(any) error

AlwaysFail returns the given error for all operations.

func CompareOptions

func CompareOptions() cmp.Options

CompareOptions returns common options for comparing Kubernetes objects. By default, ignores metadata and status.

func CompareSpecOnly

func CompareSpecOnly() cmp.Options

CompareSpecOnly returns options for comparing only Spec fields. Ignores all metadata and status.

func FailKeyAfterNCalls

func FailKeyAfterNCalls(n int, err error) func(client.ObjectKey) error

FailKeyAfterNCalls returns an ObjectKey failure function that fails after N successful calls. Use for OnGet.

func FailObjAfterNCalls

func FailObjAfterNCalls(n int, err error) func(client.Object) error

FailObjAfterNCalls returns an Object failure function that fails after N successful calls. Use for OnCreate, OnUpdate, OnDelete, OnPatch, OnDeleteAllOf, OnStatusUpdate, OnStatusPatch.

func FailObjListAfterNCalls

func FailObjListAfterNCalls(n int, err error) func(client.ObjectList) error

FailObjListAfterNCalls returns an ObjectList failure function that fails after N successful calls. Use for OnList.

func FailOnKeyName

func FailOnKeyName(name string, err error) func(client.ObjectKey) error

FailOnKeyName returns an error if the key name matches.

func FailOnNamespace

func FailOnNamespace(namespace string, err error) func(client.Object) error

FailOnNamespace returns an error if the namespace matches.

func FailOnNamespacedKeyName

func FailOnNamespacedKeyName(name, namespace string, err error) func(client.ObjectKey) error

FailOnNamespacedKeyName returns an error if both the key name and namespace match.

func FailOnObjectName

func FailOnObjectName(name string, err error) func(client.Object) error

FailOnObjectName returns an error if the object name matches.

func IgnoreDeploymentRuntimeFields

func IgnoreDeploymentRuntimeFields() cmp.Option

IgnoreDeploymentRuntimeFields ignores Deployment status fields that are updated at runtime by the Deployment controller.

func IgnoreDeploymentSpecDefaults

func IgnoreDeploymentSpecDefaults() cmp.Option

IgnoreDeploymentSpecDefaults ignores DeploymentSpec fields that have Kubernetes defaults applied.

func IgnoreMetaRuntimeFields

func IgnoreMetaRuntimeFields() cmp.Options

IgnoreMetaRuntimeFields returns cmp.Options that ignore runtime-generated Kubernetes fields. Use this when comparing expected vs actual Kubernetes resources in tests.

func IgnoreObjectMetaCompletely

func IgnoreObjectMetaCompletely() cmp.Option

IgnoreObjectMetaCompletely ignores the entire ObjectMeta (use when you only care about Spec). Uses a filter function to match any struct field named "ObjectMeta".

func IgnorePVCRuntimeFields

func IgnorePVCRuntimeFields() cmp.Option

IgnorePVCRuntimeFields ignores PVC fields that are set by Kubernetes at runtime: the pvc-protection finalizer and the default VolumeMode.

func IgnorePodSpecDefaults

func IgnorePodSpecDefaults() cmp.Option

IgnorePodSpecDefaults ignores all PodSpec and Container defaults applied by Kubernetes, including ImagePullPolicy. Use when you only care about explicitly set fields.

func IgnorePodSpecDefaultsExceptPullPolicy

func IgnorePodSpecDefaultsExceptPullPolicy() cmp.Option

IgnorePodSpecDefaultsExceptPullPolicy ignores PodSpec defaults but preserves ImagePullPolicy for verification. Use when you want to assert ImagePullPolicy is correct.

func IgnoreProbeDefaults

func IgnoreProbeDefaults() cmp.Option

IgnoreProbeDefaults ignores Probe and HTTPGetAction fields that are populated with Kubernetes defaults by the API server, such as Scheme ("HTTP"), TimeoutSeconds (1), SuccessThreshold (1), and FailureThreshold (3).

func IgnoreServiceRuntimeFields

func IgnoreServiceRuntimeFields() cmp.Option

IgnoreServiceRuntimeFields ignores Service fields that are assigned at runtime by Kubernetes.

func IgnoreStatefulSetRuntimeFields

func IgnoreStatefulSetRuntimeFields() cmp.Option

IgnoreStatefulSetRuntimeFields ignores StatefulSet status fields that are updated at runtime by the StatefulSet controller.

func IgnoreStatefulSetSpecDefaults

func IgnoreStatefulSetSpecDefaults() cmp.Option

IgnoreStatefulSetSpecDefaults ignores StatefulSetSpec fields that have Kubernetes defaults applied.

func IgnoreStatus

func IgnoreStatus() cmp.Option

IgnoreStatus ignores the Status subresource completely.

Because cmpopts.IgnoreFields requires the first param to be an actual struct type, this uses a filter function to match any struct field named "Status".

func NewFakeClientWithFailures

func NewFakeClientWithFailures(baseClient client.Client, config *FailureConfig) client.Client

NewFakeClientWithFailures creates a fake client that can be configured to fail operations. This is useful for testing error handling paths in controllers.

func Obj

func Obj[T any, PT interface {
	*T
	client.Object
}](name, namespace string) PT

Obj creates a client.Object with the given name and namespace. This is a convenience helper for deletion testing and other scenarios where you need to reference an object by name/namespace only.

Example:

watcher.WaitForDeletion(testutil.Obj[appsv1.StatefulSet]("etcd", "default"))

func SetUpClient

func SetUpClient(t testing.TB, cfg *rest.Config, scheme *runtime.Scheme) client.Client

SetUpClient creates a direct Kubernetes client (non-cached).

IMPORTANT: This creates a client that bypasses the manager's cache and reads directly from the API server. This is different from mgr.GetClient() which uses cached reads (same as controllers).

For most tests, use mgr.GetClient() instead - it tests what controllers actually see.

Note about when to use SetUpClient:

  1. Testing cache synchronization: - Verify what's actually in the API server vs what the cache sees - Useful when debugging "why doesn't my controller see this resource?"

  2. Strong consistency requirements: - Need immediate reads after writes (no cache lag) - Testing race conditions or timing-sensitive behavior

  3. Comparing cached vs direct reads: directClient := SetUpClient(t, cfg, scheme) cachedClient := mgr.GetClient() // Create resource cachedClient.Create(ctx, obj) // Direct read (guaranteed to see it) directClient.Get(ctx, key, &actual) // Cached read (might lag slightly) cachedClient.Get(ctx, key, &fromCache)

func SetUpEnvtest

func SetUpEnvtest(t testing.TB, opts ...EnvtestOption) *rest.Config

SetUpEnvtest starts Kubernetes API server for testing.

This requires the envtest binary to be available. Kubebuilder setup helps downloading the relevant binaries into bin directory using the make script.

By default, envtest is automatically cleaned up when the test finishes.

Options:

  • WithKubeconfig(): Generates kubeconfig for debugging and keeps envtest running
  • WithCRDPaths(paths...): Sets CRD directory paths (required)

func SetUpEnvtestManager

func SetUpEnvtestManager(
	t testing.TB,
	scheme *runtime.Scheme,
	opts ...EnvtestOption,
) manager.Manager

SetUpEnvtestManager is a convenience function that combines SetUpEnvtest, SetUpManager, and StartManager into a single call.

This is the recommended way to set up integration tests:

mgr := testutil.SetUpEnvtestManager(t, scheme)
c := mgr.GetClient()

// Setup your controller
reconciler := &YourReconciler{Client: c, Scheme: scheme}
reconciler.SetupWithManager(mgr)

Note: envtest does not support garbage collection (cascading deletion via owner references), because it only runs kube-apiserver and etcd, not kube-controller-manager where the garbage collector controller runs. To test cascading deletion, use kind based testing instead.

Also, for more control, you can use the individual functions separately.

func SetUpManager

func SetUpManager(t testing.TB, cfg *rest.Config, scheme *runtime.Scheme) manager.Manager

SetUpManager creates a controller-runtime manager for testing.

The manager is created but NOT started - you must call StartManager separately. This separation allows you to register controllers and configure the manager before starting it.

Typical usage:

mgr := testutil.SetUpManager(t, cfg, scheme)
// Register your controllers
reconciler := &YourReconciler{Client: mgr.GetClient(), Scheme: scheme}
reconciler.SetupWithManager(mgr)
// Start the manager
testutil.StartManager(t, mgr)

Or use SetUpEnvtestManager for a combined setup.

func StartManager

func StartManager(t testing.TB, mgr manager.Manager)

StartManager starts the manager using t.Context().

The manager runs in a background goroutine. If the manager fails to start (e.g., controller registration errors), the error will be reported via t.Errorf and is visible in test output.

Note: t.Context() gets cancelled when the test finishes, which cleanly stops the manager.

Types

type EnvtestOption

type EnvtestOption func(*envtestConfig)

EnvtestOption is a functional option for configuring envtest setup.

func WithCRDPaths

func WithCRDPaths(paths ...string) EnvtestOption

WithCRDPaths sets the CRD directory paths for envtest.

func WithKubeconfig

func WithKubeconfig() EnvtestOption

WithKubeconfig generates a kubeconfig file for debugging and keeps envtest running.

type ErrUnwatchedKinds

type ErrUnwatchedKinds struct {
	Kinds []string
}

ErrUnwatchedKinds is returned when trying to wait for resource kinds that aren't being watched by the ResourceWatcher.

func (*ErrUnwatchedKinds) Error

func (e *ErrUnwatchedKinds) Error() string

type FailureConfig

type FailureConfig struct {
	// OnGet is called before Get operations. Return non-nil to fail the operation.
	OnGet func(key client.ObjectKey) error

	// OnList is called before List operations. Return non-nil to fail the operation.
	OnList func(list client.ObjectList) error

	// OnCreate is called before Create operations. Return non-nil to fail the operation.
	OnCreate func(obj client.Object) error

	// OnUpdate is called before Update operations. Return non-nil to fail the operation.
	OnUpdate func(obj client.Object) error

	// OnPatch is called before Patch operations. Return non-nil to fail the operation.
	OnPatch func(obj client.Object) error

	// OnDelete is called before Delete operations. Return non-nil to fail the operation.
	OnDelete func(obj client.Object) error

	// OnDeleteAllOf is called before DeleteAllOf operations. Return non-nil to fail the operation.
	OnDeleteAllOf func(obj client.Object) error

	// OnStatusUpdate is called before Status().Update() operations. Return non-nil to fail the operation.
	OnStatusUpdate func(obj client.Object) error

	// OnStatusPatch is called before Status().Patch() operations. Return non-nil to fail the operation.
	OnStatusPatch func(obj client.Object) error
}

FailureConfig configures when the fake client should return errors. Each field is a function that receives the object/key and returns an error if the operation should fail.

type KubeConfigProvider

type KubeConfigProvider interface {
	KubeConfig() ([]byte, error)
}

KubeConfigProvider abstracts the KubeConfig generation.

type Option

type Option func(rw *ResourceWatcher)

func WithCmpOpts

func WithCmpOpts(opts ...cmp.Option) Option

WithCmpOpts sets the default comparison options for WaitForMatch operations. These options are passed to go-cmp's Diff function.

func WithExtraResource

func WithExtraResource(objs ...client.Object) Option

WithExtraResource adds a watch for an additional resource type. The object should be a pointer reference to the struct such as a custom resource.

If you need to watch multiple resources, you can provide the list of resources.

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout sets the default timeout for WaitForMatch operations. If not set, defaults to 5 seconds.

type ResourceEvent

type ResourceEvent struct {
	Type      string // "ADDED", "UPDATED", "DELETED"
	Kind      string // "Service", "StatefulSet", "Deployment", etc.
	Name      string
	Namespace string
	Object    client.Object // The actual object (type-assert to specific type)
	Time      time.Time
}

ResourceEvent represents a Kubernetes resource event.

type ResourceWatcher

type ResourceWatcher struct {
	// contains filtered or unexported fields
}

ResourceWatcher collects events from multiple resource types.

func NewResourceWatcher

func NewResourceWatcher(
	t testing.TB,
	ctx context.Context,
	mgr manager.Manager,
	opts ...Option,
) *ResourceWatcher

NewResourceWatcher creates a new ResourceWatcher and automatically watches Service, StatefulSet, and Deployment resources.

func (*ResourceWatcher) Count

func (rw *ResourceWatcher) Count() int

Count returns the total number of events collected.

func (*ResourceWatcher) EventCh

func (rw *ResourceWatcher) EventCh() <-chan ResourceEvent

EventCh returns the channel for receiving events directly.

This provides a low level control to check the events directly, but in most cases, there are other functions that can handle more robustly, such as WaitForMatch.

func (*ResourceWatcher) Events

func (rw *ResourceWatcher) Events() []ResourceEvent

Events returns a snapshot of all collected events at the current time.

This provides a low level control to check the events directly, but in most cases, there are other functions that can handle more robustly, such as WaitForMatch.

func (*ResourceWatcher) ForKind

func (rw *ResourceWatcher) ForKind(kind string) []ResourceEvent

ForKind returns events for a specific resource kind.

This provides a low level control to check the events directly, but in most cases, there are other functions that can handle more robustly, such as WaitForMatch.

func (*ResourceWatcher) ForName

func (rw *ResourceWatcher) ForName(name string) []ResourceEvent

ForName returns events for a specific resource name (across all kinds).

This provides a low level control to check the events directly, but in most cases, there are other functions that can handle more robustly, such as WaitForMatch.

func (*ResourceWatcher) ResetCmpOpts

func (rw *ResourceWatcher) ResetCmpOpts()

ResetCmpOpts resets the comparison options to nil (no special options).

func (*ResourceWatcher) ResetTimeout

func (rw *ResourceWatcher) ResetTimeout()

ResetTimeout resets the timeout to the default value (5 seconds).

func (*ResourceWatcher) SetCmpOpts

func (rw *ResourceWatcher) SetCmpOpts(opts ...cmp.Option)

SetCmpOpts updates the default comparison options for WaitForMatch operations. This can be called at any time to change the options for subsequent calls.

func (*ResourceWatcher) SetTimeout

func (rw *ResourceWatcher) SetTimeout(timeout time.Duration)

SetTimeout updates the default timeout for WaitForMatch operations. This can be called at any time to change the timeout for subsequent calls.

func (*ResourceWatcher) WaitForDeletion

func (rw *ResourceWatcher) WaitForDeletion(objs ...client.Object) error

WaitForDeletion waits for one or more resources to be deleted (receive DELETED events). This checks that resources were fully removed from the cluster, not just marked for deletion with DeletionTimestamp.

Uses the watcher's configured timeout. The timeout applies to the entire operation, not per resource. All resources share the same deadline.

Example:

// Delete parent resource
client.Delete(ctx, etcd)

// Wait for owned resources to be cascade deleted
err := watcher.WaitForDeletion(
    testutil.Obj[appsv1.StatefulSet]("etcd", "default"),
    testutil.Obj[corev1.Service]("etcd", "default"),
    testutil.Obj[corev1.Service]("etcd-headless", "default"),
)

func (*ResourceWatcher) WaitForEventType

func (rw *ResourceWatcher) WaitForEventType(
	kind, eventType string,
) (*ResourceEvent, error)

WaitForEventType waits for an event with specific kind and type (ADDED, UPDATED, DELETED). Returns the first matching event, or error on timeout.

Uses the watcher's configured timeout (set via SetTimeout or WithTimeout).

func (*ResourceWatcher) WaitForMatch

func (rw *ResourceWatcher) WaitForMatch(expected ...client.Object) error

WaitForMatch waits for one or more resources to match the expected objects using go-cmp comparison. Returns nil when all matched, error on timeout.

Uses the watcher's configured timeout and comparison options (set via SetTimeout/SetCmpOpts or during initialization with WithTimeout/WithCmpOpts).

The timeout applies to the entire operation, not per resource. All resources share the same deadline.

First checks existing events for early return, then subscribes to new events. Note that, when the desired state is found, this terminates prematurely regardless of how the future events change the actual state of the object.

When multiple objects are provided, waits for all of them to match.

Example:

watcher.SetCmpOpts(testutil.CompareSpecOnly()...)
expectedSts := &appsv1.StatefulSet{
    Spec: appsv1.StatefulSetSpec{Replicas: ptr.To(int32(3))},
}
expectedSvc := &corev1.Service{...}
err := watcher.WaitForMatch(expectedSts, expectedSvc)
if err != nil {
    t.Errorf("Resources never reached expected state: %v", err)
}

type UserAdder

type UserAdder interface {
	AddUser(user envtest.User, opts *rest.Config) (KubeConfigProvider, error)
}

UserAdder abstracts adding a user to the control plane for testing.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL