Skip to main content
The fake Kubernetes client provides a complete mock implementation of the Kubernetes dynamic client, allowing you to test Kubernetes-related code without needing an actual cluster.

Features

Full CRUD Operations

Create, Read, Update, and Delete operations for all resources

Custom Resources

Support for standard Kubernetes resources and CRDs

Label & Field Selectors

Filter resources using labels and field selectors

Watch Operations

Watch functionality with event generation

Realistic Status

Automatic status generation for resources

Namespace Support

Full namespace isolation and management

Installation & Setup

The fake client is included with openshift-python-wrapper. To use it in your tests:
from ocp_resources.resource import get_client

# Create a fake client
fake_client = get_client(fake=True)

# Use it like a real Kubernetes dynamic client
api = fake_client.resources.get(api_version="v1", kind="Pod")

Basic Usage

Creating Resources

Create resources using standard Kubernetes manifests:
# Create a Pod
pod = {
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
        "name": "test-pod",
        "namespace": "default"
    },
    "spec": {
        "containers": [{
            "name": "nginx",
            "image": "nginx:latest"
        }]
    }
}

created_pod = api.create(body=pod, namespace="default")
print(created_pod.metadata.name)  # test-pod
print(created_pod.status.phase)  # Running

Listing Resources

# List all pods in a namespace
pods = api.get(namespace="default")
for pod in pods.items:
    print(pod.metadata.name)

# List with label selector
pods = api.get(namespace="default", label_selector="app=nginx")
for pod in pods.items:
    print(f"{pod.metadata.name} - {pod.metadata.labels}")

Updating Resources

# Get and update a pod
pod = api.get(name="test-pod", namespace="default")
pod.metadata.labels = {"app": "nginx", "version": "v1"}

updated = api.patch(
    name="test-pod",
    namespace="default",
    body=pod
)

print(updated.metadata.labels)  # {'app': 'nginx', 'version': 'v1'}

Deleting Resources

# Delete a pod
api.delete(name="test-pod", namespace="default")

# Verify deletion
try:
    api.get(name="test-pod", namespace="default")
except Exception:
    print("Pod successfully deleted")

Watch Operations

# Watch for resource changes
count = 0
for event in api.watch(namespace="default", timeout=5):
    print(f"{event['type']}: {event['object'].metadata.name}")
    count += 1
    if count >= 3:
        break

Configuring Resource Ready Status

You can configure resources to be in a “not ready” state for testing failure scenarios.

Using Annotations

Add the fake-client.io/ready annotation to any resource:
# Create a Pod that's not ready
pod = {
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
        "name": "not-ready-pod",
        "namespace": "default",
        "annotations": {
            "fake-client.io/ready": "false"  # Pod will be not ready
        }
    },
    "spec": {
        "containers": [{
            "name": "nginx",
            "image": "nginx:latest"
        }]
    }
}

created_pod = api.create(body=pod, namespace="default")
print(created_pod.status.phase)  # Pending
print(created_pod.status.conditions)  # Ready condition will be False
For Pods specifically, you can also use fake-client.io/pod-ready annotation for backward compatibility.

Using Spec Field

Alternatively, use readyStatus in the spec:
deployment = {
    "apiVersion": "apps/v1",
    "kind": "Deployment",
    "metadata": {
        "name": "not-ready-deployment",
        "namespace": "default"
    },
    "spec": {
        "readyStatus": False,  # Deployment will be not ready
        "replicas": 3,
        "selector": {"matchLabels": {"app": "nginx"}},
        "template": {
            "metadata": {"labels": {"app": "nginx"}},
            "spec": {
                "containers": [{
                    "name": "nginx",
                    "image": "nginx:latest"
                }]
            }
        }
    }
}

created = api.create(body=deployment, namespace="default")
print(created.status.readyReplicas)  # 0
print(created.status.conditions)  # Available condition will be False

Resource-Specific Behavior

Different resources behave differently when not ready:
Show as not ready with containers in waiting state:
  • status.phase: Pending
  • Container states show as waiting
  • Ready condition is False
Show 0 ready replicas and unavailable condition:
  • status.readyReplicas: 0
  • status.availableReplicas: 0
  • Available condition is False
Show as Terminating phase:
  • status.phase: Terminating
Show Ready condition as False:
  • Ready condition set to False
  • Resource-specific status fields reflect not-ready state

Custom Resources

The fake client automatically supports any Custom Resource:
# Access a custom resource
crd_api = fake_client.resources.get(
    api_version="example.com/v1",
    kind="MyCustomResource"
)

# Create custom resource
custom_resource = {
    "apiVersion": "example.com/v1",
    "kind": "MyCustomResource",
    "metadata": {"name": "my-resource"},
    "spec": {"foo": "bar"}
}

created = crd_api.create(body=custom_resource)
print(created.spec.foo)  # bar

Advanced Features

Automatic Namespace Creation

Namespaces are automatically created when you create resources in non-existent namespaces:
# This will auto-create the "new-namespace" namespace
pod = api.create(body=pod_manifest, namespace="new-namespace")

# Verify namespace was created
ns_api = fake_client.resources.get(api_version="v1", kind="Namespace")
namespace = ns_api.get(name="new-namespace")
print(namespace.status.phase)  # Active

Realistic Resource Status

Resources automatically get realistic status fields:
# Pods get running status
pod = api.get(name="test-pod", namespace="default")
print(pod.status.phase)  # "Running"
print(pod.status.containerStatuses[0].ready)  # True
print(pod.status.containerStatuses[0].state.running)  # Present

# Deployments get replica counts
deployment = deployment_api.get(name="test-deployment", namespace="default")
print(deployment.status.readyReplicas)  # Matches spec.replicas
print(deployment.status.conditions[0].type)  # "Available"
print(deployment.status.conditions[0].status)  # "True"

OpenShift Resources

The client includes OpenShift-specific resources:
# Work with OpenShift routes
route_api = fake_client.resources.get(
    api_version="route.openshift.io/v1",
    kind="Route"
)

route = {
    "apiVersion": "route.openshift.io/v1",
    "kind": "Route",
    "metadata": {"name": "my-route", "namespace": "default"},
    "spec": {
        "to": {"kind": "Service", "name": "my-service"},
        "port": {"targetPort": "http"}
    }
}

created_route = route_api.create(body=route, namespace="default")
print(created_route.spec.host)  # Auto-generated host

Testing Patterns

Using Pytest Fixtures

import pytest
from ocp_resources.resource import get_client
from ocp_resources.pod import Pod

@pytest.fixture(scope="class")
def fake_client():
    """Fixture that provides a fake client for testing"""
    return get_client(fake=True)

@pytest.fixture(scope="class")
def test_pod(fake_client):
    """Create a test pod"""
    pod = Pod(
        client=fake_client,
        name="test-pod",
        namespace="default",
        containers=[{"name": "nginx", "image": "nginx:latest"}]
    )
    deployed = pod.deploy()
    yield deployed
    pod.clean_up()

def test_pod_creation(test_pod):
    """Test pod is created successfully"""
    assert test_pod.exists
    assert test_pod.name == "test-pod"
    assert test_pod.status == Pod.Status.RUNNING

Testing Resource Lifecycle

def test_resource_lifecycle(fake_client):
    """Test complete resource lifecycle"""
    from ocp_resources.namespace import Namespace
    
    # Create
    ns = Namespace(client=fake_client, name="test-ns")
    ns.deploy()
    assert ns.exists
    
    # Read
    assert ns.status == Namespace.Status.ACTIVE
    
    # Update
    ns_dict = ns.instance.to_dict()
    ns_dict["metadata"]["labels"] = {"env": "test"}
    ns.update(resource_dict=ns_dict)
    assert ns.labels["env"] == "test"
    
    # Delete
    ns.clean_up(wait=False)
    assert not ns.exists

Testing Error Conditions

def test_not_ready_pod(fake_client):
    """Test handling of not-ready pods"""
    from ocp_resources.pod import Pod
    
    pod = Pod(
        client=fake_client,
        name="broken-pod",
        namespace="default",
        containers=[{"name": "app", "image": "myapp:latest"}],
        annotations={"fake-client.io/ready": "false"}
    )
    
    deployed = pod.deploy()
    
    # Pod should not be ready
    with pytest.raises(TimeoutError):
        pod.wait_for_condition(
            condition=Pod.Condition.READY,
            status=Pod.Condition.Status.TRUE,
            timeout=5
        )

Limitations

The fake client is designed for testing and has some limitations:
No real networking or pod execution occurs. Containers don’t actually run.
Watch implementation provides immediate events only, not continuous streaming.
Only metadata.name and metadata.namespace field selectors are supported.
No admission webhooks or validation beyond basic structure checking.
Status updates are simplified and may not reflect all real-world complexities.

Complete Testing Example

import pytest
from ocp_resources.resource import get_client
from ocp_resources.namespace import Namespace
from ocp_resources.pod import Pod
from ocp_resources.service import Service

@pytest.fixture(scope="class")
def fake_client():
    return get_client(fake=True)

class TestApplication:
    """Test a complete application deployment"""
    
    @pytest.fixture(scope="class")
    def namespace(self, fake_client):
        ns = Namespace(client=fake_client, name="test-app")
        ns.deploy()
        yield ns
        ns.clean_up()
    
    def test_deploy_pod(self, fake_client, namespace):
        """Test deploying a pod"""
        pod = Pod(
            client=fake_client,
            name="app-pod",
            namespace=namespace.name,
            containers=[{
                "name": "app",
                "image": "myapp:v1.0",
                "ports": [{"containerPort": 8080}]
            }],
            labels={"app": "myapp"}
        )
        
        deployed = pod.deploy()
        assert deployed.exists
        assert deployed.status == Pod.Status.RUNNING
        
        # Cleanup
        pod.clean_up()
    
    def test_deploy_service(self, fake_client, namespace):
        """Test deploying a service"""
        service = Service(
            client=fake_client,
            name="app-service",
            namespace=namespace.name,
            selector={"app": "myapp"},
            ports=[{"port": 80, "targetPort": 8080}]
        )
        
        deployed = service.deploy()
        assert deployed.exists
        assert deployed.spec.selector["app"] == "myapp"
        
        # Cleanup
        service.clean_up()

Next Steps

Testing Guide

Learn comprehensive testing patterns and best practices

Class Generator

Generate wrapper classes for your resources

Build docs developers (and LLMs) love