Search code examples
unit-testinggokubernetesmocking

Why do I receive an incorrect result in the fake K8s client?


Below I have a really simple test and a minimal working example.

In the test I am inserting 4 jobs into my fake client. I then delete all jobs that match both the labels AND the namespace. I marked the jobs that should be deleted with // match. The other 2 jobs should stay.

When I run this I get

Error:          "map[App1:%!s(bool=true) App2:%!s(bool=true) OtherApp:%!s(bool=true)]" should have 2 item(s), but has 3

Why do App1, App2 and OtherApp not get deleted? Is this a bug in the fake k8s client?

package somepackage

import (
    "context"
    "testing"

    "github.com/stretchr/testify/require"
    batchv1 "k8s.io/api/batch/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/labels"
    "k8s.io/apimachinery/pkg/runtime"

    fake2 "k8s.io/client-go/kubernetes/fake"
)

func TestFunc(t *testing.T) {
    myNamespace := "namespace"
    myLabels := map[string]string{"key": "value"}

    allJobs := []runtime.Object{
        &batchv1.Job{ // match
            ObjectMeta: metav1.ObjectMeta{
                Name:      "App1",
                Namespace: myNamespace,
                Labels:    myLabels,
            },
        }, &batchv1.Job{ // match
            ObjectMeta: metav1.ObjectMeta{
                Name:      "App2",
                Namespace: myNamespace,
                Labels:    myLabels,
            },
        }, &batchv1.Job{ // other namespace
            ObjectMeta: metav1.ObjectMeta{
                Name:      "App3",
                Namespace: "otherNamespace",
                Labels:    myLabels,
            },
        }, &batchv1.Job{ // other labels
            ObjectMeta: metav1.ObjectMeta{
                Name:      "OtherApp",
                Namespace: myNamespace,
                Labels:    map[string]string{"other": "label"},
            },
        },
    }

    cliSet := fake2.NewSimpleClientset(allJobs...)

    err := cliSet.BatchV1().Jobs(myNamespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{
        LabelSelector: labels.FormatLabels(myLabels),
    })

    jobs, err := cliSet.BatchV1().Jobs(myNamespace).List(context.Background(), metav1.ListOptions{}) 

    require.NoError(t, err)

    jobNameSet := map[string]bool{}
    for _, job := range jobs.Items {
        jobNameSet[job.Name] = true
    }
    require.Len(t, jobNameSet, 2, "Expected 2 jobs after deletion")
    require.NotContains(t, jobNameSet, "App1")
    require.NotContains(t, jobNameSet, "App2")
    require.Contains(t, jobNameSet, "App3", "Should exist, because other namespace")
    require.Contains(t, jobNameSet, "OtherApp", "Should exist, because other labels")

}

Solution

  • You can use Delete:

        err = cliSet.BatchV1().Jobs(myNamespace).Delete(context.TODO(), "App2", metav1.DeleteOptions{})
    

    Clientset is backed by a very simple object tracker that doesn't support DeleteCollection.