Mocking NewManager() in unittests

177 Views Asked by At

I have a custom kubernetes operator written in Go. When writing unittests I came across the need to have a fake manager initialised. The manager is created with NewManager() function of the "sigs.k8s.io/controller-runtime" package. When run locally that is not a problem, but it is when running in a pipeline.

import (
    ctrl "sigs.k8s.io/controller-runtime"
    // other imports
)

var _ = Describe("ProvideNewController", func() {
    var (
        reconciler  reconcile.Reconciler
        customUtils controllers.CustomUtils // local utils package
        mockCC      *controllers.MockCommandClient
        errResult   error
    )
    When("ProvideNewController is called", func() {
        BeforeEach(func() {
            logger := logr.Discard()
            recorder := record.NewFakeRecorder(100)
            mockCC = &controllers.MockCommandClient{}
            customUtils = controllers.CustomUtils{
                CommandClient: mockCC,
            }
            scheme := scheme.Scheme
            scheme.AddKnownTypes(operatorV1alpha1.GroupVersion, &operatorV1alpha1.Custom{})
            managerOptions := ctrl.Options{
                Scheme:           scheme,
                Logger:           ctrl.Log.WithName("controllers").WithName("custom"),
                WebhookServer:    webhook.NewServer(webhook.Options{Port: 9443}),
                LeaderElectionID: "someValue",
            }
            fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects().Build()
            reconciler = NewReconciler(fakeClient, customUtils, logger, scheme, recorder)
            mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), managerOptions)
            if err != nil {
                common.FailTestSetup(err)
            }
            errResult = ProvideNewController(mgr, reconciler)
        })
        It("should set up a controller with the specified resource types", func() {
            Expect(errResult).ToNot(HaveOccurred())
        })
    })
})

Now the line mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), managerOptions) crashes the tests completely and ungracefully because ctrl.GetConfigOrDie() fails to read a kubeconfig file, which is expected because it really isn't present on the pod of the CI/CD runner.

I'm new to Go, so I'm not aware of all the options there are. From what I've seen, if I was to mock the whole struct and provide it mocked like that as an argument for the NewManager, I would've had to mock all the methods that struct implements and I don't see that as a good approach.

Is there a way for me to mock the config file or provide it programmatically, so I don't have to edit the pipeline and provide it from there?

2

There are 2 best solutions below

0
Matteo On

You could use the envtest package of the sigs.k8s.io controller-runtime repo. See the writing test section under the Configuring envtest for integration tests doc page:

import sigs.k8s.io/controller-runtime/pkg/envtest

//specify testEnv configuration
testEnv = &envtest.Environment{
    CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
}

//start testEnv
cfg, err = testEnv.Start()

//write test logic

//stop testEnv
err = testEnv.Stop()

0
Богдан Божић On

And so it came to be that I am answering my own question. GetConfigOrDie() returns a rest.Config{} object, so instead of using the function call as an argument and mocking or faking everything around it, or passing a fake config file into the CI/CD, I just passed an empty &rest.Config{} as an argument for the function call and it worked. I'm kinda ashamed for not trying this as a first option...

mgr, err := ctrl.NewManager(&rest.Config{}, managerOptions)