nespondev
nespondev

Reputation: 73

mocking kubernetes cluster in golang end to end tests?

I have a series of end to end tests written in golang. The functionality being tested connects to a kubernetes cluster via helm. My question is - is there a nice way of creating a dummy kubernetes cluster. E.g. here is some of the code being tested:

import (
    "helm.sh/helm/v3/pkg/action"
    "helm.sh/helm/v3/pkg/release"
)

func (h *Helm) GetMyRelease(name string) (*release.Release, error) {
    cmd := action.NewGet(h.config)
    release, err := cmd.Run(name)
    if err != nil {
        return nil, err
    }
    return release, nil
}

The Run function above is like so

func (g *Get) Run(name string) (*release.Release, error) {
    if err := g.cfg.KubeClient.IsReachable(); err != nil {
        return nil, err
    }

    return g.cfg.releaseContent(name, g.Version)
}

in the helm golang package.

I've tried creating a local kubernetes cluster using k3d but it doesn't seem to work as I'd expect - I cannot create a 'real' KubeClient through it.
Any suggestions? Thanks!

Upvotes: 1

Views: 506

Answers (1)

Deltics
Deltics

Reputation: 23056

Are you trying to test Kubernetes and/or Helm or the behaviour of your code when using the Helm API?

If it is your code you are trying to test, you need to create an abstraction of the API(s) you are using so that you can mock the responses to exercise the behaviour of your code under all conditions that may be presented to it by that external dependency.

This might seem like a lot of work, but the future will thank you for it; with abstractions in place, your tests can be efficiently executed without having to stand up and tear down any external dependencies. You can also be certain to cover all possible behaviours of that external dependency, even in cases where that behaviour might be difficult (or even impossible/impractical) to achieve with an actual cluster to test against.

There are different ways to achieve this abstraction; one might result in something similar to this:

import (
    "helm.sh/helm/v3/pkg/action"
    "helm.sh/helm/v3/pkg/release"
)

type cmdRunner interface {
   Run(string) (*release.Release, error)
}

var getAction = func(cfg *action.Configuration) cmdRunner { return action.NewGet(cfg) }

func (h *Helm) GetMyRelease(name string) (*release.Release, error) {
    cmd := getAction(h.config)
    release, err := cmd.Run(name)
    if err != nil {
        return nil, err
    }
    return release, nil
}

This should be functionally identical to what you had before but the Helm API is now called via the getAction function variable, rather than directly.

The function variable is not exported and cannot be manipulated outside of this package, but that's OK for unit tests (internal integration tests that test your project holistically from outside the package might require you to revisit this, but that's beyond the scope of the question and this answer).

The function returns an interface describing the Run method that you intend to invoke on the returned value. The initial value of the function variable is a function which calls the action.NewGet() function.

With this in place, you can write a unit test which replaces the getAction function with a stand-in, returning a value which implements the desired interface in a way that allows you to manipulate the returned values to create the conditions necessary to exercise your code.

This might look something similar to this:

type fakeRunner struct {
   release *release.Release
   error
}

func (fake *fakeRunner) Run(string) (*release.Release, error) {
   return fake.release, fake.error
} 

func TestGetMyRelease(t *testing.T) {
   // ARRANGE
   runner := &fakeRunner{}

   og := getAction
   defer func() { getAction = og }()
   getAction = func(*action.Configuration) { return runner }

   // ...
}

It should be evident how you can now set a return value and/or an error in the fakeRunner and exercise your code to test the behaviour when presented with different results from the Helm API, without needing an actual cluster to run against.

Upvotes: -1

Related Questions