A JUnit 5 testing library that provides a Kubernetes mock server for testing Kubernetes client applications
—
Full in-memory Kubernetes API implementation supporting all standard CRUD operations, resource watching, and advanced features like patches and finalizers.
Main CRUD dispatcher providing full in-memory Kubernetes API functionality.
/**
* CRUD-mode dispatcher providing full in-memory Kubernetes API
* Stores resources in memory and handles all standard Kubernetes operations
*/
public class KubernetesCrudDispatcher extends CrudDispatcher
implements KubernetesCrudPersistence, CustomResourceAware {
/**
* Default constructor with empty custom resource list
*/
public KubernetesCrudDispatcher();
/**
* Constructor with initial custom resource definitions
* @param crdContexts List of custom resource definition contexts
*/
public KubernetesCrudDispatcher(List<CustomResourceDefinitionContext> crdContexts);
}Handle HTTP POST requests for resource creation with proper validation and storage.
/**
* Handle resource creation requests
* Validates resources and stores them with generated resource versions
* @param request RecordedRequest containing resource data
* @return MockResponse with created resource or error
*/
public MockResponse handleCreate(RecordedRequest request);Supported Features:
Usage Examples:
@EnableKubernetesMockClient(crud = true)
class CreateOperationsTest {
KubernetesClient client;
@Test
void testPodCreation() {
// Create a pod
Pod pod = new PodBuilder()
.withNewMetadata()
.withName("test-pod")
.withNamespace("default")
.endMetadata()
.withNewSpec()
.addNewContainer()
.withName("app")
.withImage("nginx")
.endContainer()
.endSpec()
.build();
Pod created = client.pods().inNamespace("default").resource(pod).create();
assertNotNull(created.getMetadata().getResourceVersion());
assertNotNull(created.getMetadata().getCreationTimestamp());
assertEquals("test-pod", created.getMetadata().getName());
}
@Test
void testGeneratedNameCreation() {
// Create pod with generateName
Pod pod = new PodBuilder()
.withNewMetadata()
.withGenerateName("test-pod-")
.withNamespace("default")
.endMetadata()
.build();
Pod created = client.pods().inNamespace("default").resource(pod).create();
assertTrue(created.getMetadata().getName().startsWith("test-pod-"));
}
}Handle HTTP GET requests for retrieving individual resources and resource lists.
/**
* Handle resource retrieval requests
* Supports both individual resource GET and list operations
* @param path Request path identifying the resource(s)
* @return MockResponse with resource data or 404 if not found
*/
public MockResponse handleGet(String path);Supported Features:
/api/v1, /apis/apps/v1)Usage Examples:
@Test
void testResourceRetrieval() {
// Create a resource first
Pod pod = client.pods().inNamespace("default")
.resource(new PodBuilder().withNewMetadata().withName("test").endMetadata().build())
.create();
// Retrieve by name
Pod retrieved = client.pods().inNamespace("default").withName("test").get();
assertEquals("test", retrieved.getMetadata().getName());
// List all pods
PodList list = client.pods().inNamespace("default").list();
assertEquals(1, list.getItems().size());
// List with label selector
client.pods().inNamespace("default").withLabel("app", "web").list();
}
@Test
void testApiDiscovery() {
// API discovery works automatically
APIResourceList v1Resources = client.getKubernetesVersion();
assertNotNull(v1Resources);
}Handle HTTP PUT requests for full resource updates with optimistic concurrency control.
/**
* Handle resource update requests
* Performs full resource replacement with resource version checking
* @param request RecordedRequest containing updated resource data
* @return MockResponse with updated resource or conflict error
*/
public MockResponse handleUpdate(RecordedRequest request);Supported Features:
Usage Examples:
@Test
void testResourceUpdate() {
// Create initial resource
Pod pod = client.pods().inNamespace("default")
.resource(new PodBuilder().withNewMetadata().withName("test").endMetadata().build())
.create();
// Update the resource
pod.getMetadata().getLabels().put("updated", "true");
Pod updated = client.pods().inNamespace("default").resource(pod).update();
assertEquals("true", updated.getMetadata().getLabels().get("updated"));
assertNotEquals(pod.getMetadata().getResourceVersion(),
updated.getMetadata().getResourceVersion());
}
@Test
void testOptimisticConcurrency() {
Pod pod = client.pods().inNamespace("default")
.resource(new PodBuilder().withNewMetadata().withName("test").endMetadata().build())
.create();
// Simulate concurrent modification
pod.getMetadata().setResourceVersion("999"); // Invalid version
assertThrows(KubernetesClientException.class, () -> {
client.pods().inNamespace("default").resource(pod).update();
});
}Handle HTTP PATCH requests supporting JSON Patch, Strategic Merge Patch, and JSON Merge Patch.
/**
* Handle resource patch requests
* Supports multiple patch formats and partial updates
* @param request RecordedRequest containing patch data and content type
* @return MockResponse with patched resource or error
*/
public MockResponse handlePatch(RecordedRequest request);Supported Patch Types:
Usage Examples:
@Test
void testStrategicMergePatch() {
// Create pod with initial labels
Pod pod = client.pods().inNamespace("default")
.resource(new PodBuilder()
.withNewMetadata()
.withName("test")
.addToLabels("env", "dev")
.endMetadata()
.build())
.create();
// Strategic merge patch to add label
Pod patch = new PodBuilder()
.withNewMetadata()
.withName("test")
.addToLabels("app", "web")
.endMetadata()
.build();
Pod patched = client.pods().inNamespace("default")
.withName("test").patch(PatchContext.of(PatchType.STRATEGIC_MERGE), patch);
// Both labels should be present
assertEquals("dev", patched.getMetadata().getLabels().get("env"));
assertEquals("web", patched.getMetadata().getLabels().get("app"));
}
@Test
void testJsonPatch() {
Pod pod = client.pods().inNamespace("default")
.resource(new PodBuilder().withNewMetadata().withName("test").endMetadata().build())
.create();
// JSON Patch operations
String jsonPatch = "[{\"op\":\"add\",\"path\":\"/metadata/labels\",\"value\":{\"patched\":\"true\"}}]";
Pod patched = client.pods().inNamespace("default")
.withName("test").patch(PatchContext.of(PatchType.JSON), jsonPatch);
assertEquals("true", patched.getMetadata().getLabels().get("patched"));
}Handle HTTP DELETE requests with proper finalizer handling and cascading deletion.
/**
* Handle resource deletion requests
* Supports finalizers, graceful deletion, and cascading options
* @param path Request path identifying the resource to delete
* @return MockResponse with deletion confirmation or resource with deletion timestamp
*/
public MockResponse handleDelete(String path);Supported Features:
Usage Examples:
@Test
void testImmediateDeletion() {
Pod pod = client.pods().inNamespace("default")
.resource(new PodBuilder().withNewMetadata().withName("test").endMetadata().build())
.create();
// Delete without finalizers - immediate removal
client.pods().inNamespace("default").withName("test").delete();
Pod deleted = client.pods().inNamespace("default").withName("test").get();
assertNull(deleted);
}
@Test
void testFinalizerDeletion() {
// Create resource with finalizer
Pod pod = new PodBuilder()
.withNewMetadata()
.withName("test")
.addToFinalizers("example.com/my-finalizer")
.endMetadata()
.build();
client.pods().inNamespace("default").resource(pod).create();
// Delete - should mark for deletion but not remove
client.pods().inNamespace("default").withName("test").delete();
Pod marked = client.pods().inNamespace("default").withName("test").get();
assertNotNull(marked.getMetadata().getDeletionTimestamp());
// Remove finalizer to complete deletion
marked.getMetadata().getFinalizers().clear();
client.pods().inNamespace("default").resource(marked).update();
// Now it should be gone
Pod gone = client.pods().inNamespace("default").withName("test").get();
assertNull(gone);
}Handle WebSocket watch requests for real-time resource change notifications.
/**
* Handle watch requests for real-time resource monitoring
* Establishes WebSocket connection and sends watch events
* @param path Request path with watch=true parameter
* @return MockResponse with WebSocket upgrade for watch stream
*/
public MockResponse handleWatch(String path);Supported Features:
Usage Examples:
@Test
void testResourceWatch() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
List<WatchEvent<Pod>> events = new ArrayList<>();
// Start watching pods
Watch watch = client.pods().inNamespace("default").watch(new Watcher<Pod>() {
@Override
public void eventReceived(Action action, Pod resource) {
events.add(new WatchEvent<>(action, resource));
latch.countDown();
}
@Override
public void onClose(WatcherException cause) {}
});
try {
// Create a pod - should trigger ADDED event
Pod pod = new PodBuilder().withNewMetadata().withName("watched").endMetadata().build();
client.pods().inNamespace("default").resource(pod).create();
// Update the pod - should trigger MODIFIED event
pod.getMetadata().getLabels().put("updated", "true");
client.pods().inNamespace("default").resource(pod).update();
// Wait for events
assertTrue(latch.await(5, TimeUnit.SECONDS));
assertEquals(2, events.size());
assertEquals(Watcher.Action.ADDED, events.get(0).getAction());
assertEquals(Watcher.Action.MODIFIED, events.get(1).getAction());
} finally {
watch.close();
}
}Combines expectations-based mocking with CRUD operations for flexible testing.
/**
* Composite dispatcher combining MockDispatcher and KubernetesCrudDispatcher
* Pre-recorded expectations take priority over CRUD operations
*/
public class KubernetesMixedDispatcher extends Dispatcher
implements Resetable, CustomResourceAware {
/**
* Constructor with responses map for expectations
* @param responses Map of request-response expectations
*/
public KubernetesMixedDispatcher(Map<ServerRequest, Queue<ServerResponse>> responses);
/**
* Constructor with custom resource contexts
* @param responses Map of request-response expectations
* @param crdContexts List of custom resource definition contexts
*/
public KubernetesMixedDispatcher(
Map<ServerRequest, Queue<ServerResponse>> responses,
List<CustomResourceDefinitionContext> crdContexts);
/**
* Dispatch requests to appropriate handler
* Checks expectations first, falls back to CRUD operations
* @param request RecordedRequest to handle
* @return MockResponse from expectations or CRUD handler
*/
public MockResponse dispatch(RecordedRequest request);
}Usage Examples:
@EnableKubernetesMockClient(crud = true)
class MixedModeTest {
KubernetesMockServer server;
KubernetesClient client;
@Test
void testMixedMode() {
// Set up specific expectation
server.expect().get()
.withPath("/api/v1/namespaces/kube-system/pods/special-pod")
.andReturn(200, new PodBuilder()
.withNewMetadata().withName("special-pod").endMetadata()
.build())
.once();
// This uses the expectation
Pod special = client.pods().inNamespace("kube-system").withName("special-pod").get();
assertEquals("special-pod", special.getMetadata().getName());
// This uses CRUD mode (no expectation set)
Pod regular = new PodBuilder().withNewMetadata().withName("regular").endMetadata().build();
client.pods().inNamespace("default").resource(regular).create();
Pod retrieved = client.pods().inNamespace("default").withName("regular").get();
assertEquals("regular", retrieved.getMetadata().getName());
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-fabric8--kubernetes-server-mock