Skip to content

Commit 402b4c5

Browse files
authored
docs: improve testing docs (#3321)
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
1 parent 1633475 commit 402b4c5

1 file changed

Lines changed: 50 additions & 49 deletions

File tree

docs/content/en/docs/documentation/testing.md

Lines changed: 50 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -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
5959
connects 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 =
153153
void 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

165212
For 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

272319
The `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
274321
explicit 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

324325
Operator reconciliation is often a multi-step process. A realistic test exercises your reconciler

0 commit comments

Comments
 (0)