@@ -55,7 +55,7 @@ when(context.getSecondaryResource(Deployment.class)).thenReturn(Optional.of(depl
5555
5656## Integration Testing with ` LocallyRunOperatorExtension `
5757
58- For integration tests, JOSDK provides a JUnit 5 extension that starts your operator locally and
58+ For integration tests, JOSDK provides a JUnit extension that starts your operator locally and
5959connects it to a real Kubernetes cluster (e.g. a local Kind or Minikube cluster). It automatically:
6060
6161- Creates an isolated test namespace
@@ -153,13 +153,60 @@ LocallyRunOperatorExtension extension =
153153void shouldReconcileExactlyOnce() {
154154 extension. create(testResource());
155155
156- await(). untilAsserted(() - > {
156+ await(). pollDelay( Duration . ofSeconds( 1 )) . untilAsserted(() - > {
157157 var reconciler = extension. getReconcilerOfType(MyReconciler . class);
158158 assertThat(reconciler. getReconcileCount()). isEqualTo(1 );
159159 });
160160}
161161```
162162
163+ ## Using Fabric8 ` @KubeAPITest ` for Realistic API Testing
164+
165+ For tests that need a more realistic Kubernetes API (including watches, status subresources, and
166+ server-side apply), the Fabric8 client provides the
167+ [ ` @KubeAPITest ` ] ( https://github.com/fabric8io/kubernetes-client/blob/main/doc/kube-api-test.md )
168+ annotation. It starts a lightweight Kubernetes API server that behaves more closely to a real cluster than
169+ the mock server (see below). The API Server starts quickly, so it is suitable to run it from unit tests, even separately
170+ for each test case if needed. In addition to that comes handy if your CI does not support running tools like
171+ Kind and/or Minikube.
172+
173+ ``` xml
174+ <dependency >
175+ <groupId >io.fabric8</groupId >
176+ <artifactId >kubernetes-junit-jupiter</artifactId >
177+ <version >${fabric8-client.version}</version >
178+ <scope >test</scope >
179+ </dependency >
180+ ```
181+
182+ ``` java
183+ @KubeAPITest // runs a Kubernetes API Server binary
184+ class MyReconcilerKubeAPITest {
185+
186+ static KubernetesClient client; // injects a client
187+
188+ @RegisterExtension
189+ LocallyRunOperatorExtension extension =
190+ LocallyRunOperatorExtension . builder()
191+ .withConfigurationService(o - > o. withCloseClientOnStop(false ))
192+ // KubeAPITest does not support deleting namespaces
193+ .waitForNamespaceDeletion(false )
194+ .withKubernetesClient(client) // using the injected client
195+ .withReconciler(new MyReconciler ())
196+ .build();
197+
198+ @Test
199+ void shouldReconcileExactlyOnce () {
200+ extension. create(testResource());
201+
202+ await(). pollDelay(Duration . ofSeconds(1 )). untilAsserted(() - > {
203+ var reconciler = extension. getReconcilerOfType(MyReconciler . class);
204+ assertThat(reconciler. getReconcileCount()). isEqualTo(1 );
205+ });
206+ }
207+ }
208+ ```
209+
163210## Testing with a Cluster-Deployed Operator
164211
165212For end-to-end tests where the operator runs as a container in the cluster (e.g. to test the
@@ -270,55 +317,9 @@ class MyReconcilerMockTest {
270317```
271318
272319The ` crud = true ` flag enables automatic CRUD behavior: resources you create are stored and can be
273- retrieved, updated, and deleted, simulating a real API server . Without it, you would need to set up
320+ retrieved, updated, and deleted, simulating a real API Server . Without it, you would need to set up
274321explicit request/response expectations.
275322
276- ## Using Fabric8 ` @KubeAPITest ` for Realistic API Testing
277-
278- For tests that need a more realistic Kubernetes API (including watches, status subresources, and
279- server-side apply), the Fabric8 client provides the
280- [ ` @KubeAPITest ` ] ( https://github.com/fabric8io/kubernetes-client/blob/main/doc/kube-api-test.md )
281- annotation. It starts a lightweight Kubernetes API server that behaves more closely to a real cluster than
282- the mock server. The API Server starts quickly, so it is suitable to run it from unit tests, even separately
283- for each test case if needed. In addition to that comes handy if your CI does not support running tools like
284- Kind and/or Minikube.
285-
286- ``` xml
287- <dependency >
288- <groupId >io.fabric8</groupId >
289- <artifactId >kubernetes-junit-jupiter</artifactId >
290- <version >${fabric8-client.version}</version >
291- <scope >test</scope >
292- </dependency >
293- ```
294-
295- ``` java
296- @KubeAPITest
297- class MyReconcilerKubeAPITest {
298-
299- KubernetesClient client;
300-
301- @Test
302- void shouldHandleStatusUpdates () {
303- // The API server supports watches, SSA, and status subresources
304- client. resource(testCRD()). create();
305- client. resource(testCustomResource()). create();
306-
307- var reconciler = new MyReconciler ();
308- var context = mock(Context . class);
309- when(context. getClient()). thenReturn(client);
310-
311- var resource = client. resources(MyCustomResource . class)
312- .withName(" test" ). get();
313- reconciler. reconcile(resource, context);
314-
315- var updated = client. resources(MyCustomResource . class)
316- .withName(" test" ). get();
317- assertThat(updated. getStatus(). getState()). isEqualTo(" Ready" );
318- }
319- }
320- ```
321-
322323## Multi-Reconciliation Testing Pattern
323324
324325Operator reconciliation is often a multi-step process. A realistic test exercises your reconciler
0 commit comments