Skip to content

Proposal: a way to inject errors into fake Client for testing  #1048

@jpatel-pivotal

Description

@jpatel-pivotal

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/designCategorizes issue or PR as related to design.lifecycle/rottenDenotes an issue or PR that has aged beyond stale and will be auto-closed.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions