Skip to content
This repository was archived by the owner on Nov 18, 2020. It is now read-only.

Commit 7187102

Browse files
committed
pkg/controller/memcached/memcached_controller_test.go: unit test using fake client
1 parent d368a8d commit 7187102

File tree

3 files changed

+149
-2
lines changed

3 files changed

+149
-2
lines changed

memcached-operator/pkg/controller/memcached/memcached_controller.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,20 @@ func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Res
137137
// List the pods for this memcached's deployment
138138
podList := &corev1.PodList{}
139139
labelSelector := labels.SelectorFromSet(labelsForMemcached(memcached.Name))
140-
listOps := &client.ListOptions{Namespace: memcached.Namespace, LabelSelector: labelSelector}
140+
listOps := &client.ListOptions{
141+
Namespace: memcached.Namespace,
142+
LabelSelector: labelSelector,
143+
// HACK: due to a fake client bug, ListOptions.Raw.TypeMeta must be
144+
// explicitly populated for testing.
145+
//
146+
// See https://github.com/kubernetes-sigs/controller-runtime/issues/168
147+
Raw: &metav1.ListOptions{
148+
TypeMeta: metav1.TypeMeta{
149+
Kind: "Memcached",
150+
APIVersion: cachev1alpha1.SchemeGroupVersion.Version,
151+
},
152+
},
153+
}
141154
err = r.client.List(context.TODO(), listOps, podList)
142155
if err != nil {
143156
reqLogger.Error(err, "Failed to list pods.", "Memcached.Namespace", memcached.Namespace, "Memcached.Name", memcached.Name)
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package memcached
2+
3+
import (
4+
"context"
5+
"math/rand"
6+
"reflect"
7+
"strconv"
8+
"testing"
9+
10+
cachev1alpha1 "github.com/operator-framework/operator-sdk-samples/memcached-operator/pkg/apis/cache/v1alpha1"
11+
12+
appsv1 "k8s.io/api/apps/v1"
13+
corev1 "k8s.io/api/core/v1"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"k8s.io/apimachinery/pkg/runtime"
16+
"k8s.io/apimachinery/pkg/types"
17+
"k8s.io/client-go/kubernetes/scheme"
18+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
19+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
20+
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
21+
)
22+
23+
// TestMemcachedController runs ReconcileMemcached.Reconcile() against a
24+
// fake client that tracks a Memcached object.
25+
func TestMemcachedController(t *testing.T) {
26+
// Set the logger to development mode for verbose logs.
27+
logf.SetLogger(logf.ZapLogger(true))
28+
29+
var (
30+
name = "memcached-operator"
31+
namespace = "memcached"
32+
replicas int32 = 3
33+
)
34+
35+
// A Memcached resource with metadata and spec.
36+
memcached := &cachev1alpha1.Memcached{
37+
ObjectMeta: metav1.ObjectMeta{
38+
Name: name,
39+
Namespace: namespace,
40+
},
41+
Spec: cachev1alpha1.MemcachedSpec{
42+
Size: replicas, // Set desired number of Memcached replicas.
43+
},
44+
}
45+
// Objects to track in the fake client.
46+
objs := []runtime.Object{
47+
memcached,
48+
}
49+
50+
// Register operator types with the runtime scheme.
51+
s := scheme.Scheme
52+
s.AddKnownTypes(cachev1alpha1.SchemeGroupVersion, memcached)
53+
// Create a fake client to mock API calls.
54+
cl := fake.NewFakeClient(objs...)
55+
// Create a ReconcileMemcached object with the scheme and fake client.
56+
r := &ReconcileMemcached{client: cl, scheme: s}
57+
58+
// Mock request to simulate Reconcile() being called on an event for a
59+
// watched resource .
60+
req := reconcile.Request{
61+
NamespacedName: types.NamespacedName{
62+
Name: name,
63+
Namespace: namespace,
64+
},
65+
}
66+
res, err := r.Reconcile(req)
67+
if err != nil {
68+
t.Fatalf("reconcile: (%v)", err)
69+
}
70+
// Check the result of reconciliation to make sure it has the desired state.
71+
if !res.Requeue {
72+
t.Error("reconcile did not requeue request as expected")
73+
}
74+
75+
// Check if deployment has been created and has the correct size.
76+
dep := &appsv1.Deployment{}
77+
err = cl.Get(context.TODO(), req.NamespacedName, dep)
78+
if err != nil {
79+
t.Fatalf("get deployment: (%v)", err)
80+
}
81+
dsize := *dep.Spec.Replicas
82+
if dsize != replicas {
83+
t.Errorf("dep size (%d) is not the expected size (%d)", dsize, replicas)
84+
}
85+
86+
// Create the 3 expected pods in namespace and collect their names to check
87+
// later.
88+
podLabels := labelsForMemcached(name)
89+
pod := corev1.Pod{
90+
ObjectMeta: metav1.ObjectMeta{
91+
Namespace: namespace,
92+
Labels: podLabels,
93+
},
94+
}
95+
podNames := make([]string, 3)
96+
for i := 0; i < 3; i++ {
97+
pod.ObjectMeta.Name = name + ".pod." + strconv.Itoa(rand.Int())
98+
podNames[i] = pod.ObjectMeta.Name
99+
if err = cl.Create(context.TODO(), pod.DeepCopy()); err != nil {
100+
t.Fatalf("create pod %d: (%v)", i, err)
101+
}
102+
}
103+
104+
// Reconcile again so Reconcile() checks pods and updates the Memcached
105+
// resources' Status.
106+
res, err = r.Reconcile(req)
107+
if err != nil {
108+
t.Fatalf("reconcile: (%v)", err)
109+
}
110+
if res != (reconcile.Result{}) {
111+
t.Error("reconcile did not return an empty Result")
112+
}
113+
114+
// Get the updated Memcached object.
115+
memcached = &cachev1alpha1.Memcached{}
116+
err = r.client.Get(context.TODO(), req.NamespacedName, memcached)
117+
if err != nil {
118+
t.Errorf("get memcached: (%v)", err)
119+
}
120+
121+
// Ensure Reconcile() updated the Memcached's Status as expected.
122+
nodes := memcached.Status.Nodes
123+
if !reflect.DeepEqual(podNames, nodes) {
124+
t.Errorf("pod names %v did not match expected %v", nodes, podNames)
125+
}
126+
}

memcached-operator/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/client.go

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)