CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-fabric8--kubernetes-server-mock

A JUnit 5 testing library that provides a Kubernetes mock server for testing Kubernetes client applications

Pending
Overview
Eval results
Files

crud-operations.mddocs/

CRUD Operations

Full in-memory Kubernetes API implementation supporting all standard CRUD operations, resource watching, and advanced features like patches and finalizers.

Capabilities

KubernetesCrudDispatcher

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);
}

Create Operations

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:

  • Resource validation and schema checking
  • Automatic metadata generation (resource version, creation timestamp)
  • Namespace validation and defaulting
  • Name generation for resources without names
  • Conflict detection for duplicate resources
  • Custom resource creation support

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-"));
    }
}

Read Operations

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:

  • Individual resource retrieval by name
  • Resource list operations with label and field selectors
  • Namespace-scoped and cluster-scoped resources
  • API discovery endpoints (e.g., /api/v1, /apis/apps/v1)
  • Resource metadata endpoints
  • Pagination support with continue tokens

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);
}

Update Operations

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:

  • Full resource replacement
  • Resource version conflict detection
  • Metadata preservation and updates
  • Status subresource updates
  • Scale subresource updates
  • Custom resource updates

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();
    });
}

Patch Operations

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:

  • JSON Patch (RFC 6902)
  • Strategic Merge Patch (Kubernetes-specific)
  • JSON Merge Patch (RFC 7396)
  • Server-side Apply patches

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"));
}

Delete Operations

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:

  • Immediate deletion for resources without finalizers
  • Finalizer-based deletion workflow
  • Graceful deletion periods
  • Cascade deletion options
  • Bulk deletion with label selectors

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);
}

Watch Operations

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:

  • Resource-specific watches
  • Namespace-scoped and cluster-scoped watches
  • Label and field selector filtering
  • Resume from resource version
  • Watch event types: ADDED, MODIFIED, DELETED

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();
    }
}

Mixed Mode Dispatcher

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

docs

attribute-extraction.md

crud-handlers.md

crud-operations.md

custom-resources.md

index.md

junit-integration.md

mock-server-management.md

websocket-operations.md

tile.json