Skip to content

Commit bdb9391

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 * 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 bdb9391

File tree

7 files changed

+145
-214
lines changed

7 files changed

+145
-214
lines changed

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

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

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

2423
. "github.com/onsi/ginkgo/v2"
@@ -83,30 +82,19 @@ var _ = Describe("Memcached Controller", func() {
8382
})
8483
Expect(err).NotTo(HaveOccurred())
8584
By("Checking if Deployment was successfully created in the reconciliation")
86-
Eventually(func() error {
85+
Eventually(func(g Gomega) {
8786
found := &appsv1.Deployment{}
88-
return k8sClient.Get(ctx, typeNamespacedName, found)
89-
}, time.Minute, time.Second).Should(Succeed())
87+
g.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())
88+
}).Should(Succeed())
9089

9190
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
91+
Eventually(func(g Gomega) {
92+
conditions := []metav1.Condition{}
93+
g.Expect(memcached.Status.Conditions).To(ContainElement(
94+
HaveField("Type", Equal(typeAvailableMemcached)), &conditions))
95+
g.Expect(conditions).To(HaveLen(1), "Multiple conditions of type %s", typeAvailableMemcached)
96+
g.Expect(conditions[0].Status).To(Equal(metav1.ConditionTrue), "condition %s", typeAvailableMemcached)
97+
g.Expect(conditions[0].Reason).To(Equal("Reconciling"), "condition %s", typeAvailableMemcached)
11098
}, time.Minute, time.Second).Should(Succeed())
11199
})
112100
})

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

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ func (sp *Sample) updateControllerTest() {
5151
filepath.Join(sp.ctx.Dir, file),
5252
"\"context\"",
5353
`"context"
54-
"fmt"
5554
"time"`,
5655
)
5756
hackutils.CheckError("add imports", err)
@@ -78,30 +77,19 @@ func (sp *Sample) updateControllerTest() {
7877
`// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.
7978
// Example: If you expect a certain status condition after reconciliation, verify it here.`,
8079
`By("Checking if Deployment was successfully created in the reconciliation")
81-
Eventually(func() error {
80+
Eventually(func(g Gomega) {
8281
found := &appsv1.Deployment{}
83-
return k8sClient.Get(ctx, typeNamespacedName, found)
84-
}, time.Minute, time.Second).Should(Succeed())
82+
g.Expect(k8sClient.Get(ctx, typeNamespacedName, found)).To(Succeed())
83+
}).Should(Succeed())
8584
8685
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
86+
Eventually(func(g Gomega) {
87+
conditions := []metav1.Condition{}
88+
g.Expect(memcached.Status.Conditions).To(ContainElement(
89+
HaveField("Type", Equal(typeAvailableMemcached)), &conditions))
90+
g.Expect(conditions).To(HaveLen(1), "Multiple conditions of type %s", typeAvailableMemcached)
91+
g.Expect(conditions[0].Status).To(Equal(metav1.ConditionTrue), "condition %s", typeAvailableMemcached)
92+
g.Expect(conditions[0].Reason).To(Equal("Reconciling"), "condition %s", typeAvailableMemcached)
10593
}, time.Minute, time.Second).Should(Succeed())`,
10694
)
10795
hackutils.CheckError("add spec apis", err)

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

Lines changed: 25 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,23 @@ 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+
Eventually(func(g Gomega) {
188+
conditions := []metav1.Condition{}
189+
g.Expect({{ lower .Resource.Kind }}.Status.Conditions).To(ContainElement(
190+
HaveField("Type", Equal(typeAvailable{{ .Resource.Kind }})), &conditions))
191+
g.Expect(conditions).To(HaveLen(1), "Multiple conditions of type %s", typeAvailable{{ .Resource.Kind }})
192+
g.Expect(conditions[0].Status).To(Equal(metav1.ConditionTrue), "condition %s", typeAvailable{{ .Resource.Kind }})
193+
g.Expect(conditions[0].Reason).To(Equal("Reconciling"), "condition %s", typeAvailable{{ .Resource.Kind }})
194+
}).Should(Succeed())
204195
})
205196
})
206197
})

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

Lines changed: 25 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,23 @@ 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+
Eventually(func(g Gomega) {
135+
conditions := []metav1.Condition{}
136+
g.Expect(busybox.Status.Conditions).To(ContainElement(
137+
HaveField("Type", Equal(typeAvailableBusybox)), &conditions))
138+
g.Expect(conditions).To(HaveLen(1), "Multiple conditions of type %s", typeAvailableBusybox)
139+
g.Expect(conditions[0].Status).To(Equal(metav1.ConditionTrue), "condition %s", typeAvailableBusybox)
140+
g.Expect(conditions[0].Reason).To(Equal("Reconciling"), "condition %s", typeAvailableBusybox)
141+
}).Should(Succeed())
151142
})
152143
})
153144
})

0 commit comments

Comments
 (0)