summary
Writing unit tests is always painful for developers. The main reason for this is that, generally, unit tests (functional unit tests) should not use any physical components / running instances of the application. For example, unit tests of API SDK should not have any running API instances. That's why it's crucial to simulate application instances, which is also the trick!
In this article, we'll learn how to use the fake client set of the client go package to simulate the Kubernetes client. Let's start!
Create a package named client and a package named client go file. go defines a structure called client, which holds Kubernetes Clientset.
Kubernetes Clientset
-
Clients that contain groups.
-
Is a struct Interface used to implement an Interface called Kubernetes.
-
NewForConfig() is the constructor Interface of Kubernetes and returns the Interface of Clientset object. Moving on, let's define the Client in the Client called the public structure go
// Client is the structure that holds the Kubernetes client set type Client struct { Clientset kubernetes.Interface }
Create a method that calls the CreatePod Client structure to create a Client in a given namespace Create a pod in the go file as shown in the same file
// The CreatePod method creates a pod in the cluster referenced by the client func (c Client) CreatePod(pod *v1.Pod) (*v1.Pod, error) { pod, err := c.Clientset.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) if err != nil { klog.Errorf("Error occured while creating pod %s: %s", pod.Name, err.Error()) return nil, err } klog.Infof("Pod %s is succesfully created", pod.Name) return pod, nil }
Complete client The go file looks like this:
package client import ( "context" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" ) type Client struct { Clientset kubernetes.Interface } func (c Client) CreatePod(pod *v1.Pod) (*v1.Pod, error) { pod, err := c.Clientset.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) if err != nil { klog.Errorf("Error occured while creating pod %s: %s", pod.Name, err.Error()) return nil, err } klog.Infof("Pod %s is succesfully created", pod.Name) return pod, nil }
Now, let's implement a main Go consume the CreatePod method above. To this end, there are the following steps
- Create clientset
- Load Client structure
- Define pod resource object
Call CreatePod to call the client object
Complete main The go file looks like this:
package main import ( "fmt" client "github.com/kubernetes-sdk-for-go-101/pkg/client" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "k8s.io/klog/v2" ) func main() { kubeconfig := "<path to kubeconfig file>" config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { panic(err.Error()) } clientset, err := kubernetes.NewForConfig(config) if err != nil { panic(err.Error()) } client := client.Client{ Clientset: clientset, } pod := &v1.Pod{ TypeMeta: metav1.TypeMeta{ Kind: "Pod", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: "test-pod", Namespace: "default", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Name: "nginx", Image: "nginx", ImagePullPolicy: "Always", }, }, }, } pod, err = client.CreatePod(pod) if err != nil { fmt.Printf("%s", err) } klog.Infof("Pod %s has been successfully created", pod.Name) }
When we main Go runs the file for the running cluster, and we will eventually create a pod called by test-pod in the default namespace.
Now, let's write a unit test using Go's testing package. Here, to create the Kubernetes Clientset, we use the constructor of NewSimpleClientSet() from the client Go / kubernetes package of the make package instead of the NewForConfig() constructor.
Any difference?
The NewForConfig() constructor returns the actual value of the ClientSet that has clients of each Kubernetes group and runs on the actual cluster. The NewSimpleClientSet() constructor returns the set of clients that will respond with the provided object. It is supported by a very simple object tracker that creates, updates, and deletes as is without applying any validation and / or default values.
It should be noted that the Clientsetfake package also implements the Kubernetes interface. Designed to be embedded in the structure for a default implementation. This makes it easier to forge the method you want to test. Our main_ test. I want this
package main import ( "fmt" "testing" client "github.com/kubernetes-sdk-for-go-101/pkg/client" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" testclient "k8s.io/client-go/kubernetes/fake" ) func TestCreatePod(t *testing.T) { var client client.Client client.Clientset = testclient.NewSimpleClientset() pod := &v1.Pod{ TypeMeta: metav1.TypeMeta{ Kind: "Pod", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: "test-pod", Namespace: "default", }, Spec: v1.PodSpec{ Containers: []v1.Container{ { Name: "nginx", Image: "nginx", ImagePullPolicy: "Always", }, }, }, } _, err := client.CreatePod(pod) if err != nil { fmt.Print(err.Error()) } }
Now, if we run the test file with the go test command, CreatePod() will successfully execute the method even if it is not running on any running K8s cluster, and Get() will bypass the call by imitating it to return the same object sent to it as a parameter. The output will be as shown
This article can be used as a reference and as a basis to build a test framework or unit test package client go for applications using Kubernetes. I hope this can help you.
reference resources: https://medium.com/the-phi/mocking-the-kubernetes-client-in-go-for-unit-testing-ddae65c4302