Skip to content

Commit 322e871

Browse files
committed
Impove deploy-image controller-test.go
Fixes a weakness in the controller-test. Previously, the test did not check for duplicates. Duplicate conditions can be a problem, and this new way of writing the test ensures that this is caught early. * To(Not()) → NotTo() * Use default eventually timeouts * Eventually(func() error) → Eventually(func(g Gomega)), and then use g.Expect inside those Eventually() functions * Remove use of Eventually when nothing else is happening * Use Gomega assertions for the condition check * Check for uniqueness for the conditions * Use gomega's format string support to explain the context of failures
1 parent 27f4acc commit 322e871

File tree

7 files changed

+140
-225
lines changed

7 files changed

+140
-225
lines changed

docs/book/src/getting-started/testdata/project/internal/controller/memcached_controller_test.go

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ package controller
1818

1919
import (
2020
"context"
21-
"fmt"
22-
"time"
2321

2422
. "github.com/onsi/ginkgo/v2"
2523
. "github.com/onsi/gomega"
@@ -83,31 +81,19 @@ var _ = Describe("Memcached Controller", func() {
8381
})
8482
Expect(err).NotTo(HaveOccurred())
8583
By("Checking if Deployment was successfully created in the reconciliation")
86-
Eventually(func() error {
84+
Eventually(func(g Gomega) {
8785
found := &appsv1.Deployment{}
88-
return k8sClient.Get(ctx, typeNamespacedName, found)
89-
}, time.Minute, time.Second).Should(Succeed())
86+
g.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())
87+
}).Should(Succeed())
9088

9189
By("Checking the latest Status Condition added to the Memcached instance")
92-
Eventually(func() error {
93-
if memcached.Status.Conditions != nil &&
94-
len(memcached.Status.Conditions) != 0 {
95-
latestStatusCondition := memcached.Status.Conditions[len(memcached.Status.Conditions)-1]
96-
expectedLatestStatusCondition := metav1.Condition{
97-
Type: typeAvailableMemcached,
98-
Status: metav1.ConditionTrue,
99-
Reason: "Reconciling",
100-
Message: fmt.Sprintf(
101-
"Deployment for custom resource (%s) with %d replicas created successfully",
102-
memcached.Name,
103-
memcached.Spec.Size),
104-
}
105-
if latestStatusCondition != expectedLatestStatusCondition {
106-
return fmt.Errorf("The latest status condition added to the Memcached instance is not as expected")
107-
}
108-
}
109-
return nil
110-
}, time.Minute, time.Second).Should(Succeed())
90+
Expect(k8sClient.Get(ctx, typeNamespacedName, memcached)).To(Succeed())
91+
conditions := []metav1.Condition{}
92+
Expect(memcached.Status.Conditions).To(ContainElement(
93+
HaveField("Type", Equal(typeAvailableMemcached)), &conditions))
94+
Expect(conditions).To(HaveLen(1), "Multiple conditions of type %s", typeAvailableMemcached)
95+
Expect(conditions[0].Status).To(Equal(metav1.ConditionTrue), "condition %s", typeAvailableMemcached)
96+
Expect(conditions[0].Reason).To(Equal("Reconciling"), "condition %s", typeAvailableMemcached)
11197
})
11298
})
11399
})

hack/docs/internal/getting-started/generate_getting_started.go

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,6 @@ func (sp *Sample) UpdateTutorial() {
4848
func (sp *Sample) updateControllerTest() {
4949
file := "internal/controller/memcached_controller_test.go"
5050
err := pluginutil.ReplaceInFile(
51-
filepath.Join(sp.ctx.Dir, file),
52-
"\"context\"",
53-
`"context"
54-
"fmt"
55-
"time"`,
56-
)
57-
hackutils.CheckError("add imports", err)
58-
59-
err = pluginutil.ReplaceInFile(
6051
filepath.Join(sp.ctx.Dir, file),
6152
". \"github.com/onsi/gomega\"",
6253
`. "github.com/onsi/gomega"
@@ -78,31 +69,19 @@ func (sp *Sample) updateControllerTest() {
7869
`// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.
7970
// Example: If you expect a certain status condition after reconciliation, verify it here.`,
8071
`By("Checking if Deployment was successfully created in the reconciliation")
81-
Eventually(func() error {
72+
Eventually(func(g Gomega) {
8273
found := &appsv1.Deployment{}
83-
return k8sClient.Get(ctx, typeNamespacedName, found)
84-
}, time.Minute, time.Second).Should(Succeed())
74+
g.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())
75+
}).Should(Succeed())
8576
8677
By("Checking the latest Status Condition added to the Memcached instance")
87-
Eventually(func() error {
88-
if memcached.Status.Conditions != nil &&
89-
len(memcached.Status.Conditions) != 0 {
90-
latestStatusCondition := memcached.Status.Conditions[len(memcached.Status.Conditions)-1]
91-
expectedLatestStatusCondition := metav1.Condition{
92-
Type: typeAvailableMemcached,
93-
Status: metav1.ConditionTrue,
94-
Reason: "Reconciling",
95-
Message: fmt.Sprintf(
96-
"Deployment for custom resource (%s) with %d replicas created successfully",
97-
memcached.Name,
98-
memcached.Spec.Size),
99-
}
100-
if latestStatusCondition != expectedLatestStatusCondition {
101-
return fmt.Errorf("The latest status condition added to the Memcached instance is not as expected")
102-
}
103-
}
104-
return nil
105-
}, time.Minute, time.Second).Should(Succeed())`,
78+
Expect(k8sClient.Get(ctx, typeNamespacedName, memcached)).To(Succeed())
79+
conditions := []metav1.Condition{}
80+
Expect(memcached.Status.Conditions).To(ContainElement(
81+
HaveField("Type", Equal(typeAvailableMemcached)), &conditions))
82+
Expect(conditions).To(HaveLen(1), "Multiple conditions of type %s", typeAvailableMemcached)
83+
Expect(conditions[0].Status).To(Equal(metav1.ConditionTrue), "condition %s", typeAvailableMemcached)
84+
Expect(conditions[0].Reason).To(Equal("Reconciling"), "condition %s", typeAvailableMemcached)`,
10685
)
10786
hackutils.CheckError("add spec apis", err)
10887
}

pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller-test.go

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ import (
6868
"context"
6969
"os"
7070
"time"
71-
"fmt"
7271
7372
//nolint:golint
7473
. "github.com/onsi/ginkgo/v2"
@@ -105,14 +104,17 @@ var _ = Describe("{{ .Resource.Kind }} controller", func() {
105104
}
106105
{{ lower .Resource.Kind }} := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}
107106
107+
SetDefaultEventuallyTimeout(2 * time.Minute)
108+
SetDefaultEventuallyPollingInterval(time.Second)
109+
108110
BeforeEach(func() {
109111
By("Creating the Namespace to perform the tests")
110112
err := k8sClient.Create(ctx, namespace);
111-
Expect(err).To(Not(HaveOccurred()))
113+
Expect(err).NotTo(HaveOccurred())
112114
113115
By("Setting the Image ENV VAR which stores the Operand image")
114116
err= os.Setenv("{{ upper .Resource.Kind }}_IMAGE", "example.com/image:test")
115-
Expect(err).To(Not(HaveOccurred()))
117+
Expect(err).NotTo(HaveOccurred())
116118
117119
By("creating the custom resource for the Kind {{ .Resource.Kind }}")
118120
err = k8sClient.Get(ctx, typeNamespacedName, {{ lower .Resource.Kind }})
@@ -133,19 +135,19 @@ var _ = Describe("{{ .Resource.Kind }} controller", func() {
133135
}
134136
135137
err = k8sClient.Create(ctx, {{ lower .Resource.Kind }})
136-
Expect(err).To(Not(HaveOccurred()))
138+
Expect(err).NotTo(HaveOccurred())
137139
}
138140
})
139141
140142
AfterEach(func() {
141143
By("removing the custom resource for the Kind {{ .Resource.Kind }}")
142144
found := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}
143145
err := k8sClient.Get(ctx, typeNamespacedName, found)
144-
Expect(err).To(Not(HaveOccurred()))
146+
Expect(err).NotTo(HaveOccurred())
145147
146-
Eventually(func() error {
147-
return k8sClient.Delete(context.TODO(), found)
148-
}, 2*time.Minute, time.Second).Should(Succeed())
148+
Eventually(func(g Gomega) {
149+
g.Expect(k8sClient.Delete(context.TODO(), found)).To(Succeed())
150+
}).Should(Succeed())
149151
150152
// TODO(user): Attention if you improve this code by adding other context test you MUST
151153
// be aware of the current delete namespace limitations.
@@ -159,10 +161,10 @@ var _ = Describe("{{ .Resource.Kind }} controller", func() {
159161
160162
It("should successfully reconcile a custom resource for {{ .Resource.Kind }}", func() {
161163
By("Checking if the custom resource was successfully created")
162-
Eventually(func() error {
164+
Eventually(func(g Gomega) {
163165
found := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}
164-
return k8sClient.Get(ctx, typeNamespacedName, found)
165-
}, time.Minute, time.Second).Should(Succeed())
166+
Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())
167+
}).Should(Succeed())
166168
167169
By("Reconciling the custom resource created")
168170
{{ lower .Resource.Kind }}Reconciler := &{{ .Resource.Kind }}Reconciler{
@@ -173,34 +175,22 @@ var _ = Describe("{{ .Resource.Kind }} controller", func() {
173175
_, err := {{ lower .Resource.Kind }}Reconciler.Reconcile(ctx, reconcile.Request{
174176
NamespacedName: typeNamespacedName,
175177
})
176-
Expect(err).To(Not(HaveOccurred()))
178+
Expect(err).NotTo(HaveOccurred())
177179
178180
By("Checking if Deployment was successfully created in the reconciliation")
179-
Eventually(func() error {
181+
Eventually(func(g Gomega) {
180182
found := &appsv1.Deployment{}
181-
return k8sClient.Get(ctx, typeNamespacedName, found)
182-
}, time.Minute, time.Second).Should(Succeed())
183+
g.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())
184+
}).Should(Succeed())
183185
184186
By("Checking the latest Status Condition added to the {{ .Resource.Kind }} instance")
185-
Eventually(func() error {
186-
if {{ lower .Resource.Kind }}.Status.Conditions != nil &&
187-
len({{ lower .Resource.Kind }}.Status.Conditions) != 0 {
188-
latestStatusCondition := {{ lower .Resource.Kind }}.Status.Conditions[len({{ lower .Resource.Kind }}.Status.Conditions)-1]
189-
expectedLatestStatusCondition := metav1.Condition{
190-
Type: typeAvailable{{ .Resource.Kind }},
191-
Status: metav1.ConditionTrue,
192-
Reason: "Reconciling",
193-
Message: fmt.Sprintf(
194-
"Deployment for custom resource (%s) with %d replicas created successfully",
195-
{{ lower .Resource.Kind }}.Name,
196-
{{ lower .Resource.Kind }}.Spec.Size),
197-
}
198-
if latestStatusCondition != expectedLatestStatusCondition {
199-
return fmt.Errorf("The latest status condition added to the {{ .Resource.Kind }} instance is not as expected")
200-
}
201-
}
202-
return nil
203-
}, time.Minute, time.Second).Should(Succeed())
187+
Expect(k8sClient.Get(ctx, typeNamespacedName, {{ lower .Resource.Kind }})).To(Succeed())
188+
conditions := []metav1.Condition{}
189+
Expect({{ lower .Resource.Kind }}.Status.Conditions).To(ContainElement(
190+
HaveField("Type", Equal(typeAvailable{{ .Resource.Kind }})), &conditions))
191+
Expect(conditions).To(HaveLen(1), "Multiple conditions of type %s", typeAvailable{{ .Resource.Kind }})
192+
Expect(conditions[0].Status).To(Equal(metav1.ConditionTrue), "condition %s", typeAvailable{{ .Resource.Kind }})
193+
Expect(conditions[0].Reason).To(Equal("Reconciling"), "condition %s", typeAvailable{{ .Resource.Kind }})
204194
})
205195
})
206196
})

testdata/project-v4-multigroup/internal/controller/example.com/busybox_controller_test.go

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package examplecom
1818

1919
import (
2020
"context"
21-
"fmt"
2221
"os"
2322
"time"
2423

@@ -55,14 +54,17 @@ var _ = Describe("Busybox controller", func() {
5554
}
5655
busybox := &examplecomv1alpha1.Busybox{}
5756

57+
SetDefaultEventuallyTimeout(2 * time.Minute)
58+
SetDefaultEventuallyPollingInterval(time.Second)
59+
5860
BeforeEach(func() {
5961
By("Creating the Namespace to perform the tests")
6062
err := k8sClient.Create(ctx, namespace)
61-
Expect(err).To(Not(HaveOccurred()))
63+
Expect(err).NotTo(HaveOccurred())
6264

6365
By("Setting the Image ENV VAR which stores the Operand image")
6466
err = os.Setenv("BUSYBOX_IMAGE", "example.com/image:test")
65-
Expect(err).To(Not(HaveOccurred()))
67+
Expect(err).NotTo(HaveOccurred())
6668

6769
By("creating the custom resource for the Kind Busybox")
6870
err = k8sClient.Get(ctx, typeNamespacedName, busybox)
@@ -80,19 +82,19 @@ var _ = Describe("Busybox controller", func() {
8082
}
8183

8284
err = k8sClient.Create(ctx, busybox)
83-
Expect(err).To(Not(HaveOccurred()))
85+
Expect(err).NotTo(HaveOccurred())
8486
}
8587
})
8688

8789
AfterEach(func() {
8890
By("removing the custom resource for the Kind Busybox")
8991
found := &examplecomv1alpha1.Busybox{}
9092
err := k8sClient.Get(ctx, typeNamespacedName, found)
91-
Expect(err).To(Not(HaveOccurred()))
93+
Expect(err).NotTo(HaveOccurred())
9294

93-
Eventually(func() error {
94-
return k8sClient.Delete(context.TODO(), found)
95-
}, 2*time.Minute, time.Second).Should(Succeed())
95+
Eventually(func(g Gomega) {
96+
g.Expect(k8sClient.Delete(context.TODO(), found)).To(Succeed())
97+
}).Should(Succeed())
9698

9799
// TODO(user): Attention if you improve this code by adding other context test you MUST
98100
// be aware of the current delete namespace limitations.
@@ -106,10 +108,10 @@ var _ = Describe("Busybox controller", func() {
106108

107109
It("should successfully reconcile a custom resource for Busybox", func() {
108110
By("Checking if the custom resource was successfully created")
109-
Eventually(func() error {
111+
Eventually(func(g Gomega) {
110112
found := &examplecomv1alpha1.Busybox{}
111-
return k8sClient.Get(ctx, typeNamespacedName, found)
112-
}, time.Minute, time.Second).Should(Succeed())
113+
Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())
114+
}).Should(Succeed())
113115

114116
By("Reconciling the custom resource created")
115117
busyboxReconciler := &BusyboxReconciler{
@@ -120,34 +122,22 @@ var _ = Describe("Busybox controller", func() {
120122
_, err := busyboxReconciler.Reconcile(ctx, reconcile.Request{
121123
NamespacedName: typeNamespacedName,
122124
})
123-
Expect(err).To(Not(HaveOccurred()))
125+
Expect(err).NotTo(HaveOccurred())
124126

125127
By("Checking if Deployment was successfully created in the reconciliation")
126-
Eventually(func() error {
128+
Eventually(func(g Gomega) {
127129
found := &appsv1.Deployment{}
128-
return k8sClient.Get(ctx, typeNamespacedName, found)
129-
}, time.Minute, time.Second).Should(Succeed())
130+
g.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())
131+
}).Should(Succeed())
130132

131133
By("Checking the latest Status Condition added to the Busybox instance")
132-
Eventually(func() error {
133-
if busybox.Status.Conditions != nil &&
134-
len(busybox.Status.Conditions) != 0 {
135-
latestStatusCondition := busybox.Status.Conditions[len(busybox.Status.Conditions)-1]
136-
expectedLatestStatusCondition := metav1.Condition{
137-
Type: typeAvailableBusybox,
138-
Status: metav1.ConditionTrue,
139-
Reason: "Reconciling",
140-
Message: fmt.Sprintf(
141-
"Deployment for custom resource (%s) with %d replicas created successfully",
142-
busybox.Name,
143-
busybox.Spec.Size),
144-
}
145-
if latestStatusCondition != expectedLatestStatusCondition {
146-
return fmt.Errorf("The latest status condition added to the Busybox instance is not as expected")
147-
}
148-
}
149-
return nil
150-
}, time.Minute, time.Second).Should(Succeed())
134+
Expect(k8sClient.Get(ctx, typeNamespacedName, busybox)).To(Succeed())
135+
conditions := []metav1.Condition{}
136+
Expect(busybox.Status.Conditions).To(ContainElement(
137+
HaveField("Type", Equal(typeAvailableBusybox)), &conditions))
138+
Expect(conditions).To(HaveLen(1), "Multiple conditions of type %s", typeAvailableBusybox)
139+
Expect(conditions[0].Status).To(Equal(metav1.ConditionTrue), "condition %s", typeAvailableBusybox)
140+
Expect(conditions[0].Reason).To(Equal("Reconciling"), "condition %s", typeAvailableBusybox)
151141
})
152142
})
153143
})

0 commit comments

Comments
 (0)