Unit test client-go watch API to mock the watch events using client-go testing package

Sometimes we encounter the case where we need to simulate the watch events in order to test code that uses client-go watch API in the Kubernetes world.

In this example, we will see how to mock watch API events sequence as part of the unit testing.

Before that, let’s see how to use the fake package to client-go APIs.

In this case, we will use the pod() API. You can find the complete example at https://github.com/hrishin/k8s-client-go-examples/tree/main/examples/mock-watch-events

Test get pod by name

The following snippet initialize the fake client by feeding a pod resource.

import(
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes/fake"
)

client := fake.NewSimpleClientset(&v1.Pod{
	ObjectMeta: metav1.ObjectMeta{
		Name:      "fake",
		Namespace: "fake",
	},
})

Test it by running make test-get-pod

➜  mock-watch-events git:(mock-watch) ✗ make test-get-pod
go test -run Test_get_pod_using_fake_client -v
=== RUN   Test_get_pod_using_fake_client
    fake_client_test.go:28: Fetch the pod by pod name using the client-go API 
    fake_client_test.go:31: 	Test 0: checking the error code response
    fake_client_test.go:36: 	✓	client go has return no error.
    fake_client_test.go:39: 	Test 1: verifying the retrived pod from client-go get pod API
    fake_client_test.go:44: 	✓	client go has returned the expected pod
--- PASS: Test_get_pod_using_fake_client (0.00s)
PASS
ok  	github.com/hrishin/k8s-client-go-examples/examples/mock-watch-events	0.571s

Test pod watch events mocking

In this scenario, we will simulate pod life cycle events i.e. pod.status.phase -> {PodPending, PodUnknown, PodRunning}. Usually, we encounter such code to wait for the pod to become up & running.

To feed such events client-go provides the testing package. Following example snippet to feed the mock events for the watch API.

clients := fake.NewSimpleClientset()
watcher := watch.NewFake()
clients.PrependWatchReactor("pods", k8stest.DefaultWatchReactor(watcher, nil))

go func() {
	defer watcher.Stop()

	for i, _ := range pods {
		time.Sleep(300 * time.Millisecond)
		watcher.Add(&v1.Pod{
		   ..... // your pod definitions
		})
	}
}()

Important to note here that in clients.PrependWatchReactor("pods", k8stest.DefaultWatchReactor(watcher, nil)) method pods is the plural resource name. Giving the wrong resource name would fail mocking watch events. One of the way to get the resource name is using

kubectl api-resources | grep -i pod

NAME  SHORTNAMES APIGROUP NAMESPACED KIND
pods  po  				  true 		 Pod

Test the example by running make test-watch-pod

➜  mock-watch-events git:(mock-watch) ✗ make test-watch-pod
go test -run Test_watch_pod_using_fake_client -v
=== RUN   Test_watch_pod_using_fake_client
    fake_client_test.go:91: Watch pod updates by pod name using the client-go API 
    fake_client_test.go:97:     Test 0: checking the error code response
    fake_client_test.go:102:    ✓       client go has return no error.
    fake_client_test.go:105:    Test 1: checking watch event updates
    fake_client_test.go:115:    ✓       got a pod update event
    fake_client_test.go:122:    ✓       expecting pod phase Pending and got Pending
    fake_client_test.go:115:    ✓       got a pod update event
    fake_client_test.go:122:    ✓       expecting pod phase Unknown and got Unknown
    fake_client_test.go:115:    ✓       got a pod update event
    fake_client_test.go:122:    ✓       expecting pod phase Running and got Running
--- PASS: Test_watch_pod_using_fake_client (0.91s)
PASS
ok      github.com/hrishin/k8s-client-go-examples/examples/mock-watch-events    1.490s

I hope this post will be useful. Would like to hear your reviews, feedback or your experience. Happy programming with Kubernetes!