diff --git a/testing/client.go b/testing/client.go index d971fce..96848b8 100644 --- a/testing/client.go +++ b/testing/client.go @@ -35,18 +35,18 @@ type clientWrapper struct { var _ client.Client = &clientWrapper{} +// Deprecated NewFakeClient use NewFakeClientWrapper func NewFakeClient(scheme *runtime.Scheme, objs ...client.Object) *clientWrapper { - o := make([]runtime.Object, len(objs)) - for i := range objs { - obj := objs[i].DeepCopyObject().(client.Object) - // default to a non-zero creation timestamp - if obj.GetCreationTimestamp().Time.IsZero() { - obj.SetCreationTimestamp(metav1.NewTime(time.UnixMilli(1000))) - } - o[i] = obj - } - client := &clientWrapper{ - client: fakeclient.NewFakeClientWithScheme(scheme, o...), + builder := fakeclient.NewClientBuilder() + builder = builder.WithScheme(scheme) + builder = builder.WithObjects(prepareObjects(objs)...) + + return NewFakeClientWrapper(builder.Build()) +} + +func NewFakeClientWrapper(client client.Client) *clientWrapper { + c := &clientWrapper{ + client: client, CreateActions: []objectAction{}, UpdateActions: []objectAction{}, PatchActions: []PatchAction{}, @@ -58,22 +58,35 @@ func NewFakeClient(scheme *runtime.Scheme, objs ...client.Object) *clientWrapper reactionChain: []Reactor{}, } // generate names on create - client.AddReactor("create", "*", func(action Action) (bool, runtime.Object, error) { + c.AddReactor("create", "*", func(action Action) (bool, runtime.Object, error) { if createAction, ok := action.(CreateAction); ok { obj := createAction.GetObject() if accessor, ok := obj.(metav1.ObjectMetaAccessor); ok { objmeta := accessor.GetObjectMeta() if objmeta.GetName() == "" && objmeta.GetGenerateName() != "" { - client.genCount++ + c.genCount++ // mutate the existing obj - objmeta.SetName(fmt.Sprintf("%s%03d", objmeta.GetGenerateName(), client.genCount)) + objmeta.SetName(fmt.Sprintf("%s%03d", objmeta.GetGenerateName(), c.genCount)) } } } // never handle the action return false, nil, nil }) - return client + return c +} + +func prepareObjects(objs []client.Object) []client.Object { + o := make([]client.Object, len(objs)) + for i := range objs { + obj := objs[i].DeepCopyObject().(client.Object) + // default to a non-zero creation timestamp + if obj.GetCreationTimestamp().Time.IsZero() { + obj.SetCreationTimestamp(metav1.NewTime(time.UnixMilli(1000))) + } + o[i] = obj + } + return o } func (w *clientWrapper) AddReactor(verb, kind string, reaction ReactionFunc) { diff --git a/testing/config.go b/testing/config.go index 73b399c..dc446e3 100644 --- a/testing/config.go +++ b/testing/config.go @@ -21,6 +21,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" ) // ExpectConfig encompasses the creation of a config object using given state, captures observed @@ -41,6 +42,8 @@ type ExpectConfig struct { GivenObjects []client.Object // APIGivenObjects contains objects that are only available via an API reader instead of the normal cache APIGivenObjects []client.Object + // WithClientBuilder allows a test to modify the fake client initialization. + WithClientBuilder func(*fake.ClientBuilder) *fake.ClientBuilder // WithReactors installs each ReactionFunc into each fake clientset. ReactionFuncs intercept // each call to the clientset providing the ability to mutate the resource or inject an error. WithReactors []ReactionFunc @@ -88,13 +91,13 @@ func (c *ExpectConfig) init() { apiGivenObjects[i] = c.APIGivenObjects[i].DeepCopyObject().(client.Object) } - c.client = NewFakeClient(c.Scheme, givenObjects...) + c.client = c.createClient(givenObjects) for i := range c.WithReactors { // in reverse order since we prepend reactor := c.WithReactors[len(c.WithReactors)-1-i] c.client.PrependReactor("*", "*", reactor) } - c.apiReader = NewFakeClient(c.Scheme, apiGivenObjects...) + c.apiReader = c.createClient(apiGivenObjects) c.recorder = &eventRecorder{ events: []Event{}, scheme: c.Scheme, @@ -104,6 +107,18 @@ func (c *ExpectConfig) init() { }) } +func (c *ExpectConfig) createClient(objs []client.Object) *clientWrapper { + builder := fake.NewClientBuilder() + + builder.WithScheme(c.Scheme) + builder.WithObjects(prepareObjects(objs)...) + if c.WithClientBuilder != nil { + builder = c.WithClientBuilder(builder) + } + + return NewFakeClientWrapper(builder.Build()) +} + // Config returns the Config object. This method should only be called once. Subsequent calls are // ignored returning the Config from the first call. func (c *ExpectConfig) Config() reconcilers.Config { diff --git a/testing/reconciler.go b/testing/reconciler.go index ce50ec1..3fbe6f8 100644 --- a/testing/reconciler.go +++ b/testing/reconciler.go @@ -17,6 +17,7 @@ import ( "k8s.io/apimachinery/pkg/types" controllerruntime "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -41,6 +42,8 @@ type ReconcilerTestCase struct { // WithReactors installs each ReactionFunc into each fake clientset. ReactionFuncs intercept // each call to the clientset providing the ability to mutate the resource or inject an error. WithReactors []ReactionFunc + // WithClientBuilder allows a test to modify the fake client initialization. + WithClientBuilder func(*fake.ClientBuilder) *fake.ClientBuilder // GivenObjects build the kubernetes objects which are present at the onset of reconciliation GivenObjects []client.Object // APIGivenObjects contains objects that are only available via an API reader instead of the normal cache @@ -157,6 +160,7 @@ func (tc *ReconcilerTestCase) Run(t *testing.T, scheme *runtime.Scheme, factory Scheme: scheme, GivenObjects: tc.GivenObjects, APIGivenObjects: tc.APIGivenObjects, + WithClientBuilder: tc.WithClientBuilder, WithReactors: tc.WithReactors, GivenTracks: tc.GivenTracks, ExpectTracks: tc.ExpectTracks, diff --git a/testing/subreconciler.go b/testing/subreconciler.go index 3cc4d96..2c68eca 100644 --- a/testing/subreconciler.go +++ b/testing/subreconciler.go @@ -18,6 +18,7 @@ import ( "k8s.io/apimachinery/pkg/types" controllerruntime "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -41,6 +42,8 @@ type SubReconcilerTestCase struct { Resource client.Object // GivenStashedValues adds these items to the stash passed into the reconciler. Factories are resolved to their object. GivenStashedValues map[reconcilers.StashKey]interface{} + // WithClientBuilder allows a test to modify the fake client initialization. + WithClientBuilder func(*fake.ClientBuilder) *fake.ClientBuilder // WithReactors installs each ReactionFunc into each fake clientset. ReactionFuncs intercept // each call to the clientset providing the ability to mutate the resource or inject an error. WithReactors []ReactionFunc @@ -181,6 +184,7 @@ func (tc *SubReconcilerTestCase) Run(t *testing.T, scheme *runtime.Scheme, facto Scheme: scheme, GivenObjects: append(tc.GivenObjects, tc.Resource), APIGivenObjects: append(tc.APIGivenObjects, tc.Resource), + WithClientBuilder: tc.WithClientBuilder, WithReactors: tc.WithReactors, GivenTracks: tc.GivenTracks, ExpectTracks: tc.ExpectTracks, diff --git a/testing/webhook.go b/testing/webhook.go index 10155bb..0ef48fd 100644 --- a/testing/webhook.go +++ b/testing/webhook.go @@ -17,6 +17,7 @@ import ( "github.com/vmware-labs/reconciler-runtime/reconcilers" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) @@ -38,6 +39,8 @@ type AdmissionWebhookTestCase struct { Request *admission.Request // HTTPRequest is the http request used to create the admission request object. If not defined, a minimal request is provided. HTTPRequest *http.Request + // WithClientBuilder allows a test to modify the fake client initialization. + WithClientBuilder func(*fake.ClientBuilder) *fake.ClientBuilder // WithReactors installs each ReactionFunc into each fake clientset. ReactionFuncs intercept // each call to the clientset providing the ability to mutate the resource or inject an error. WithReactors []ReactionFunc @@ -146,6 +149,7 @@ func (tc *AdmissionWebhookTestCase) Run(t *testing.T, scheme *runtime.Scheme, fa Scheme: scheme, GivenObjects: tc.GivenObjects, APIGivenObjects: tc.APIGivenObjects, + WithClientBuilder: tc.WithClientBuilder, WithReactors: tc.WithReactors, GivenTracks: tc.GivenTracks, ExpectTracks: tc.ExpectTracks,