-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
It would be extremely helpful if the fake Client in controller-runtime had a way to inject errors that get returned to help with testing a controller's reconcile logic.
Consider the following psuedo-code below for example:
myObjectBeingReconciled, err := r.Get(ctx, req.NamespacedName)
if instanceNotFound(err) {
return noRequeue()
} else if err != nil {
return requeue(err)
}
Currently there is no easy way to unit test the handled vs. unhandled error case shown above with the fake Client. I propose that the fake Client could have a field like errorsToReturn map[errorKey]error
that could be used to hold errors to return for testing as shown below:
type errorKey struct {
action string
resourceSchema schema.GroupVersionKind
resourceKey client.ObjectKey
}
type fakeClient struct {
tracker versionedTracker
scheme *runtime.Scheme
errorsToReturn map[errorKey]error
}
Error injection could occur when the fakeClient is initialized with a constructor like func NewFakeClientWithInjectedErrors(clientScheme *runtime.Scheme, errToReturn map[errorKey]error, initObjs ...runtime.Object) client.Client
OR
via an exported method like func (c *fakeClient) InjectActionWithError(errorKey errorKey, injectedError error)
.
Using the InjectActionWithError()
we could now inject a instanceNotFound
error as well as a generic test error like errors.New("unhandled error case")
to be returned for myObjectBeingReconciled
so that we can get to 100% test coverage on the psuedo-code shown above.
//InjectActionWithError injects errorKey and error into errorsToReturn map for testing error cases.
func (c *fakeClient) InjectActionWithError(errorKey errorKey, injectedError error) {
c.errToReturn[errorKey] = injectedError
}
The fake Client has to check the errorsToReturn
map and see if a particular action is meant to return an error. If so, the injected error can be returned. If no injected errors were meant to be returned for this particular action, then it performs the intended action as before. An example get
implementation would look like:
func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
err = c.getInjectedError(errorKey)
if err != nil {
// Injected error matched so we return it
return err
}
// This particular get action was not meant to return an error
// Do the actual get as before
}
//A helper method to get injected errors when errorKey is found in errorsToReturn map for testing error cases.
func (c *fakeClient) getInjectedError(errKey errorKey) error {
return c.errToReturn[errKey]
}
This is briefly discussed in #1031