KubeResourceManager organises every resource it tracks into batches. A batch is a group of resources that were created together in a single call. During cleanup, batches are deleted in reverse creation order (LIFO — last in, first out), and all resources within a single batch are deleted concurrently.
This model mirrors how real Kubernetes deployments work: infrastructure is created first (namespaces, RBAC), then applications (Deployments, Services). On teardown, applications must be deleted before the namespaces they live in.
Every call to createResourceWithWait(T...) or createResourceWithoutWait(T...) that receives two or more resources automatically forms one batch:
// Batch 1: namespace alone
KubeResourceManager.get().createResourceWithWait(namespace);
// Batch 2: ConfigMap + ServiceAccount together
KubeResourceManager.get().createResourceWithWait(configMap, serviceAccount);On cleanup, batch 2 is deleted first (configMap and serviceAccount, concurrently), then batch 1 (namespace).
Use openBatch() to group several independent create* calls into one batch. All resources added inside the try block are treated as a single unit during deletion:
// Batch 1: namespace
KubeResourceManager.get().createResourceWithWait(namespace);
// Batch 2: multiple calls, but one batch
try (AutoCloseable ignored = KubeResourceManager.get().openBatch()) {
KubeResourceManager.get().createResourceWithWait(deployment);
KubeResourceManager.get().createResourceWithWait(service);
KubeResourceManager.get().createResourceWithWait(configMap);
}
// deployment, service, and configMap are all in batch 2 and deleted together before namespaceopenBatch() returns an AutoCloseable, so standard try-with-resources closes the batch automatically.
Consider this setup:
Batch 1 (created first): Namespace "my-app"
Batch 2 (created second): Deployment "my-app/backend", Service "my-app/backend-svc"
Batch 3 (created last): Secret "my-app/tls-secret", ConfigMap "my-app/app-config"
Cleanup order:
- Batch 3 deleted — Secret and ConfigMap deleted concurrently
- Batch 2 deleted — Deployment and Service deleted concurrently
- Batch 1 deleted — Namespace deleted
This ensures resources are removed before the namespaces that contain them.
// All resources tracked for the current test
List<HasMetadata> resources = KubeResourceManager.get().getCurrentResources();
// Number of tracked resources
int count = resources.size();When you annotate a test class with @KubernetesTest (cleanup default is AUTOMATIC) or @ResourceManager, the framework calls deleteResources() after each test method. You do not need to call it manually.
If you set cleanup = CleanupStrategy.MANUAL on @KubernetesTest, or use @ResourceManager(cleanResources = false), you are responsible for calling:
KubeResourceManager.get().deleteResources();Call it in an @AfterEach method or wherever it fits your test lifecycle.
KubeResourceManager.get().deleteResourceWithWait(myDeployment);This removes the resource from the tracker stack and waits for deletion to complete.
By default, resources within a batch are deleted asynchronously (concurrently). This is faster but may produce interleaved log output.
Disable async deletion for ordered, sequential cleanup:
@ResourceManager(asyncDeletion = false)
class OrderedCleanupTest { ... }With @KubernetesTest, asynchronous deletion is always enabled. Use @ResourceManager directly if you need synchronous deletion.
Register a callback to run after every successful resource deletion:
KubeResourceManager.get().addDeleteCallback(resource -> {
LOGGER.info("Deleted {} {}", resource.getKind(), resource.getMetadata().getName());
});Callbacks fire after the delete API call succeeds. They do not fire for failed deletions.
See also the create callback counterpart:
KubeResourceManager.get().addCreateCallback(resource -> {
if ("Namespace".equals(resource.getKind())) {
KubeUtils.labelNamespace(resource.getMetadata().getName(), "managed-by", "kubetest4j");
}
});@ResourceManager
@TestVisualSeparator
class ResourceLifecycleTest {
static {
KubeResourceManager.get().setResourceTypes(
new NamespaceType(),
new DeploymentType(),
new ServiceType()
);
}
@Test
void testBatchedLifecycle() {
Namespace ns = new NamespaceBuilder()
.withNewMetadata().withName("lifecycle-test").endMetadata()
.build();
Deployment deploy = new DeploymentBuilder()
.withNewMetadata().withName("my-app").withNamespace("lifecycle-test").endMetadata()
// ... spec ...
.build();
Service svc = new ServiceBuilder()
.withNewMetadata().withName("my-app").withNamespace("lifecycle-test").endMetadata()
// ... spec ...
.build();
// Batch 1 — namespace (deleted last)
KubeResourceManager.get().createResourceWithWait(ns);
// Batch 2 — application resources (deleted first)
KubeResourceManager.get().createResourceWithWait(deploy, svc);
// ... assertions ...
// Automatic cleanup after the test: batch 2 then batch 1
}
}- Core Module —
KubeResourceManagerAPI reference - Resource Types — Built-in and custom
ResourceTypeimplementations - JUnit Extension — Declarative cleanup via
@KubernetesTest