Skip to content

Commit e4c26fe

Browse files
committed
Add testing samples using Kubernetes Mock Server and JUnit5 annotations
Signed-off-by: Rohan Kumar <[email protected]>
1 parent fb9037b commit e4c26fe

File tree

9 files changed

+627
-3
lines changed

9 files changed

+627
-3
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ mvn exec:java -Dexec.mainClass="io.fabric8.DeploymentDemo"
2222
- [CRD YAML -> Java POJO](./fabric8-crd-java-generator-demo/README.md)
2323
- [Java POJO -> CRD YAML](./fabric8-java-crd-yaml-generator-demo/README.md)
2424

25+
### [Writing Tests Using Fabric8 Kubernetes Client](./writing-tests-with-fabric8/README.md)
26+
2527
### `kubectl` to Kubernetes Client Mapping:
2628
| kubectl | Fabric8 Kubernetes Client |
2729
| ---------------------------------------------- | ------------------------------------- |

fabric8-java-crd-yaml-generator-demo/pom.xml

-3
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212

1313
<artifactId>fabric8-java-crd-yaml-generator-demo</artifactId>
1414

15-
<properties>
16-
</properties>
17-
1815
<dependencies>
1916
<dependency>
2017
<groupId>io.fabric8</groupId>

parent/pom.xml

+21
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
1616
<exec-maven-plugin.version>1.4.0</exec-maven-plugin.version>
1717
<maven-surefire-plugin.version>3.0.0-M7</maven-surefire-plugin.version>
18+
<maven-failsafe-plugin.version>3.0.0-M7</maven-failsafe-plugin.version>
1819
<junit.version>5.9.1</junit.version>
1920
<fabric8.version>6.3.1</fabric8.version>
2021
</properties>
@@ -23,6 +24,7 @@
2324
<module>..</module>
2425
<module>../fabric8-crd-java-generator-demo</module>
2526
<module>../fabric8-java-crd-yaml-generator-demo</module>
27+
<module>../writing-tests-with-fabric8</module>
2628
</modules>
2729

2830
<dependencyManagement>
@@ -71,6 +73,25 @@
7173
<artifactId>maven-surefire-plugin</artifactId>
7274
<version>${maven-surefire-plugin.version}</version>
7375
</plugin>
76+
<plugin>
77+
<groupId>org.apache.maven.plugins</groupId>
78+
<artifactId>maven-failsafe-plugin</artifactId>
79+
<version>${maven-failsafe-plugin.version}</version>
80+
<executions>
81+
<execution>
82+
<id>integration-tests</id>
83+
<goals>
84+
<goal>integration-test</goal>
85+
<goal>verify</goal>
86+
</goals>
87+
<configuration>
88+
<includes>
89+
<include>**IT.java</include>
90+
</includes>
91+
</configuration>
92+
</execution>
93+
</executions>
94+
</plugin>
7495
</plugins>
7596
</pluginManagement>
7697
</build>

writing-tests-with-fabric8/README.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Writing tests using Fabric8 Kubernetes Client
2+
3+
This is a demo project that has a simple [PodGroupService](./src/main/java/io/fabric8/demos/tests/mockserver/PodGroupService.java) which manages lifecycle of a group of Pods sharing common labels. It has common operations like add, list, watch, delete, etc.
4+
5+
We write tests for this service using various options provided to us by Fabric8 Kubernetes Client:
6+
7+
## Kubernetes Mock Server Tests
8+
Based on [Fabric8 Kubernetes Mock Server](https://search.maven.org/artifact/io.fabric8/kubernetes-server-mock/6.3.1/jar)
9+
10+
- Using CRUD Mode : [PodGroupServiceCrudTest](./src/test/java/io/fabric8/demos/tests/mockserver/PodGroupServiceCrudTest.java)
11+
- Using Expectations Mode : [PodGroupServiceTest](./src/test/java/io/fabric8/demos/tests/mockserver/PodGroupServiceTest.java)
12+
13+
## Real Kubernetes Tests
14+
Based on [Fabric8 Kubernetes JUnit Jupiter](https://search.maven.org/artifact/io.fabric8/kubernetes-junit-jupiter/6.3.1/jar)
15+
16+
- Using Fabric8 JUnit5 Annotations : [PodGroupServiceIT](./src/test/java/io/fabric8/demos/tests/e2e/PodGroupServiceIT.java)
17+
18+
These tests require a Kubernetes instance to be running. They will execute in `k8s-e2e` profile:
19+
```shell
20+
$ mvn clean install -Pk8s-e2e
21+
```
22+

writing-tests-with-fabric8/pom.xml

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>io.fabric8</groupId>
8+
<artifactId>kubernetes-client-demo-parent</artifactId>
9+
<version>1.0-SNAPSHOT</version>
10+
<relativePath>../parent/pom.xml</relativePath>
11+
</parent>
12+
13+
<artifactId>writing-tests-with-fabric8</artifactId>
14+
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>io.fabric8</groupId>
19+
<artifactId>kubernetes-client</artifactId>
20+
<version>${fabric8.version}</version>
21+
</dependency>
22+
<dependency>
23+
<groupId>io.fabric8</groupId>
24+
<artifactId>kubernetes-server-mock</artifactId>
25+
<version>${fabric8.version}</version>
26+
<scope>test</scope>
27+
</dependency>
28+
<dependency>
29+
<groupId>io.fabric8</groupId>
30+
<artifactId>kubernetes-junit-jupiter</artifactId>
31+
<version>${fabric8.version}</version>
32+
<scope>test</scope>
33+
</dependency>
34+
<dependency>
35+
<groupId>org.junit.jupiter</groupId>
36+
<artifactId>junit-jupiter-api</artifactId>
37+
<scope>test</scope>
38+
</dependency>
39+
<dependency>
40+
<groupId>org.junit.jupiter</groupId>
41+
<artifactId>junit-jupiter-engine</artifactId>
42+
<scope>test</scope>
43+
</dependency>
44+
</dependencies>
45+
46+
<build>
47+
<plugins>
48+
<plugin>
49+
<groupId>org.apache.maven.plugins</groupId>
50+
<artifactId>maven-surefire-plugin</artifactId>
51+
</plugin>
52+
</plugins>
53+
</build>
54+
55+
<profiles>
56+
<profile>
57+
<id>k8s-e2e</id>
58+
<build>
59+
<plugins>
60+
<plugin>
61+
<groupId>org.apache.maven.plugins</groupId>
62+
<artifactId>maven-failsafe-plugin</artifactId>
63+
<version>${maven-failsafe-plugin.version}</version>
64+
</plugin>
65+
</plugins>
66+
</build>
67+
</profile>
68+
</profiles>
69+
70+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.fabric8.demos.tests.mockserver;
2+
3+
import io.fabric8.kubernetes.api.model.Pod;
4+
import io.fabric8.kubernetes.api.model.PodList;
5+
import io.fabric8.kubernetes.api.model.StatusDetails;
6+
import io.fabric8.kubernetes.client.KubernetesClient;
7+
import io.fabric8.kubernetes.client.Watch;
8+
import io.fabric8.kubernetes.client.Watcher;
9+
10+
import java.util.HashMap;
11+
import java.util.List;
12+
import java.util.Map;
13+
14+
public class PodGroupService {
15+
private final KubernetesClient kubernetesClient;
16+
private final Map<String, String> matchLabels;
17+
18+
public PodGroupService(KubernetesClient client, Map<String, String> matchLabels) {
19+
this.kubernetesClient = client;
20+
this.matchLabels = matchLabels;
21+
}
22+
23+
public PodList list() {
24+
return kubernetesClient.pods().withLabels(matchLabels).list();
25+
}
26+
27+
public int size() {
28+
return list().getItems().size();
29+
}
30+
31+
public void addToGroup(Pod newPod) {
32+
Map<String, String> newLabels = new HashMap<>();
33+
Map<String, String> oldLabels = newPod.getMetadata().getLabels();
34+
newLabels.putAll(oldLabels);
35+
newLabels.putAll(matchLabels);
36+
37+
newPod.getMetadata().setLabels(newLabels);
38+
kubernetesClient.pods().resource(newPod).createOrReplace();
39+
}
40+
41+
public List<StatusDetails> delete() {
42+
return kubernetesClient.pods()
43+
.withLabels(matchLabels)
44+
.withGracePeriod(0L)
45+
.delete();
46+
}
47+
48+
public Watch watch(Watcher<Pod> watcher) {
49+
return kubernetesClient.pods()
50+
.withLabels(matchLabels)
51+
.watch(watcher);
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package io.fabric8.demos.tests.e2e;
2+
3+
import io.fabric8.demos.tests.mockserver.PodGroupService;
4+
import io.fabric8.kubernetes.api.model.ObjectMeta;
5+
import io.fabric8.kubernetes.api.model.Pod;
6+
import io.fabric8.junit.jupiter.api.KubernetesTest;
7+
import io.fabric8.junit.jupiter.api.RequireK8sSupport;
8+
import io.fabric8.junit.jupiter.api.RequireK8sVersionAtLeast;
9+
import io.fabric8.kubernetes.api.model.PodBuilder;
10+
import io.fabric8.kubernetes.api.model.PodList;
11+
import io.fabric8.kubernetes.api.model.StatusDetails;
12+
import io.fabric8.kubernetes.client.KubernetesClient;
13+
import io.fabric8.kubernetes.client.Watch;
14+
import io.fabric8.kubernetes.client.Watcher;
15+
import io.fabric8.kubernetes.client.WatcherException;
16+
import org.junit.jupiter.api.Test;
17+
18+
import java.util.Collections;
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.concurrent.CountDownLatch;
22+
import java.util.concurrent.TimeUnit;
23+
24+
import static org.junit.jupiter.api.Assertions.assertEquals;
25+
import static org.junit.jupiter.api.Assertions.assertNotNull;
26+
import static org.junit.jupiter.api.Assertions.assertTrue;
27+
28+
@KubernetesTest
29+
@RequireK8sSupport(Pod.class)
30+
@RequireK8sVersionAtLeast(majorVersion = 1, minorVersion = 16)
31+
class PodGroupServiceIT {
32+
KubernetesClient kubernetesClient;
33+
34+
35+
@Test
36+
void list_whenPodsPresent_thenReturnList() {
37+
// Given
38+
Map<String, String> matchLabel = Collections.singletonMap("app", "list");
39+
PodGroupService podGroupService = new PodGroupService(kubernetesClient, matchLabel);
40+
// When
41+
PodList podList = podGroupService.list();
42+
43+
// Then
44+
assertNotNull(podList);
45+
}
46+
47+
@Test
48+
void addToGroup_whenPodProvided_thenShouldUpdatePod() {
49+
// Given
50+
Map<String, String> matchLabel = Collections.singletonMap("app", "add-to-group");
51+
PodGroupService podGroupService = new PodGroupService(kubernetesClient, matchLabel);
52+
Pod p1 = createNewPod("p1", "add-to-group");
53+
54+
// When
55+
podGroupService.addToGroup(p1);
56+
57+
// Then
58+
PodList podList = podGroupService.list();
59+
assertTrue(podList.getItems().stream().map(Pod::getMetadata).map(ObjectMeta::getName).anyMatch(n -> n.startsWith("p1")));
60+
}
61+
62+
@Test
63+
void size_whenPodsAbsent_thenReturnZero() {
64+
// Given
65+
PodGroupService podGroupService = new PodGroupService(kubernetesClient, Collections.singletonMap("app", "size-zero"));
66+
67+
// When
68+
int result = podGroupService.size();
69+
70+
// Then
71+
assertEquals(0, result);
72+
}
73+
74+
@Test
75+
void size_whenPodsPresent_thenReturnActualSize() {
76+
// Given
77+
PodGroupService podGroupService = new PodGroupService(kubernetesClient, Collections.singletonMap("app", "size-non-zero"));
78+
podGroupService.addToGroup(createNewPod("p1", "size-non-zero"));
79+
podGroupService.addToGroup(createNewPod("p2", "size-non-zero"));
80+
81+
// When
82+
int result = podGroupService.size();
83+
84+
// Then
85+
assertEquals(2, result);
86+
}
87+
88+
@Test
89+
void watch_whenInvoked_shouldMonitorUpdates() throws Exception {
90+
// Given
91+
PodGroupService podGroupService = new PodGroupService(kubernetesClient, Collections.singletonMap("app", "watch-test"));
92+
CountDownLatch eventReceivedLatch = new CountDownLatch(1);
93+
94+
// When
95+
try (Watch ignore = podGroupService.watch(new Watcher<>() {
96+
@Override
97+
public void eventReceived(Action action, Pod pod) {
98+
eventReceivedLatch.countDown();
99+
}
100+
101+
@Override
102+
public void onClose(WatcherException e) { }
103+
})) {
104+
podGroupService.addToGroup(createNewPod("p1-watch", "watch-test"));
105+
assertTrue(eventReceivedLatch.await(5, TimeUnit.SECONDS));
106+
}
107+
// Then
108+
assertEquals(0, eventReceivedLatch.getCount());
109+
}
110+
111+
@Test
112+
void delete_whenInvoked_shouldDeleteAllMatchingPods() {
113+
// Given
114+
PodGroupService podGroupService = new PodGroupService(kubernetesClient, Collections.singletonMap("app", "delete-test"));
115+
podGroupService.addToGroup(createNewPod("p1", "delete-test"));
116+
podGroupService.addToGroup(createNewPod("p2", "delete-test"));
117+
118+
// When
119+
List<StatusDetails> deleteStatusDetails = podGroupService.delete();
120+
int sizeAfterDelete = podGroupService.size();
121+
122+
// Then
123+
assertEquals(2, deleteStatusDetails.size());
124+
assertEquals(0, sizeAfterDelete);
125+
}
126+
127+
private Pod createNewPod(String generateName, String appLabelValue) {
128+
return new PodBuilder()
129+
.withNewMetadata().withGenerateName(generateName).withLabels(Collections.singletonMap("app", appLabelValue)).endMetadata()
130+
.withNewSpec()
131+
.addNewContainer()
132+
.withName("demo-container")
133+
.withImage("alpine:latest")
134+
.endContainer()
135+
.endSpec()
136+
.build();
137+
}
138+
}

0 commit comments

Comments
 (0)