Skip to main content

HTTP/API/Kubernetes Mock Server

Project description

HTTP/API/Kubernetes Mock Server for Python

GitHub CI codecov coveralls Supported Python versions

— Kmock-kmock…

— Who's there?

— It's me, a long awaited Kubernetes API mock server!

Why?

The main practical purpose is testing Kubernetes operators developed with Kopf, operators migrated to or from Kopf (whether Pythonic or not), and for testing Kopf itself. Kopf is a framework to write Kubernetes operators in Python: https://github.com/nolar/kopf

Developers can use KMock to test any arbitrary HTTP APIs, SDKs, or apps — even without Kubernetes nuances (simply ignore the Kubernetes functionality).

The rationale behind the library design is simple: monkey-patching is bad. It makes you test the specific implementation of your HTTP/API client, not the overall communication contract. Realistic servers are an acceptable compromise. Unlike with realistic servers, which require heavy deployment, KMock works out of the box, and the only trade-off is the overhead for the localhost network traffic & HTTP/JSON rendering & parsing. The obvious flaw: you can make mistakes in your assumptions of what is the supposed response of the server.

The rationale behind the library's DSL is simple too: tests must be brief. Brief tests require brief setup & brief assertions. Extensive logic, such as for-cycles, if-conditions, temporary variables, talking with external classes, so on — all this verbosity distracts from the test purpose, leading to fewer tests being written in total.

Status

As of Jan'26, the library is freshly released and is in early stages of its life. While the overall concept is stable, some aspects and some features might change with breaking changes. The most probable change is accessing the individual requests: via the .requests property instead of the current access via the filters (see docs/ideas.rst). This depends on the usage experience by me myself (the author) in the nearest future.

The library was originally hand-made in Nov'2023 (in the pre-AI era) to its 90% readiness, then put on pause and revived in Nov'25 with the intention to release it in any usable form as soon as possible. Over these 2 years, I could forget why I did some things and designed the others one way or another. It might feel inconsistent with the docs and examples in some minor aspects. These inconsistencies should smooth out and disappear as the library matures over time before the v1 major release (now: v0).

Explanation by examples

See many more examples for every individual feature in the docs:

import aiohttp
import kmock


async def function_under_test(base_url: str) -> None:
    async with aiohttp.ClientSession(base_url=base_url) as session:
        resp = await session.get('/')
        text = await resp.read()
        resp = await session.post('/hello', json={'name': text.decode()})
        data = await resp.json()
        return data


async def test_simple_http_server(kmock: kmock.RawHandler) -> None:
    # Setup the server side.
    kmock['get /'] << b'john'
    kmock['post /hello'] << (lambda req: {'you-are': req.params.get('name', 'anonymous')})
    never_called = kmock['/'] << b''

    # Work in the client side.
    data = await function_under_test(str(kmock.url))
    assert data == {'you-are': 'john'}

    # Check the server side.
    assert len(kmock) == 2
    assert len(kmock['get']) == 1
    assert len(kmock['post']) == 1
    assert kmock['post'][0].data == {'name': 'john'}

Even live streaming is possible.

import datetime
import asyncio
import aiohttp
import freezegun
import kmock


@freezegun.freeze_time("2020-01-01T00:00:00")
async def test_k8s_out_of_the_box(kmock: kmock.RawHandler) -> None:
    kmock['/'] << (
        b'hello', lambda: asyncio.sleep(1), b', world!\n',
        {'key': 'val'},
        lambda: [(f"{i}\n".encode(), asyncio.sleep(1)) for i in range(3)],
        ...  # live continuation
    )

    async def pulse():
        while True:
            # Broadcast to every streaming request (any method, any URL).
            kmock[...] << (lambda: datetime.datetime.now(tz=datetime.UTC).isoformat(), ...)
            await asyncio.sleep(1)

    asyncio.create_task(pulse())  # we do not clean it up here for brevity

    async with aiohttp.ClientSession(base_url='http://localhost', read_timeout=5) as session:
        resp = await session.get('/')
        text = await resp.read()  # this might take some time

    assert text == b'hello, world!\n{"key": "val"}\n3…\n2…\n1…\n2020-01-01T00:00:05'

And even an out-of-box Kubernetes stateful server:

import aiohttp
import kmock
import pytest

@pytest.fixture
def k8surl() -> str:
  return 'http://localhost'

async def test_k8s_out_of_the_box(kmock: kmock.RawHandler, k8surl: str) -> None:
    async with aiohttp.ClientSession(base_url=k8surl) as session:
        pod1 = {'metadata': {'name': 'pod1'}, 'spec': {'key': 'val'}}
        pod2 = {'metadata': {'name': 'pod1'}, 'spec': {'key': 'val'}}
        await session.post('/api/v1/namespace/default/pods', json=pod1)
        await session.post('/api/v1/namespace/default/pods', json=pod2)
        resp = await session.get('/api/v1/namespace/default/pods')
        data = await resp.json()
        assert data['items'] == [pod1, pod2]

    assert len(kmock[kmock.LIST]) == 1
    assert len(kmock[kmock.resource['pods']]) == 3
    assert kmock[kmock.resource('v1/pods')][-1].method == 'GET'

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kmock-0.7.tar.gz (938.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kmock-0.7-py3-none-any.whl (77.6 kB view details)

Uploaded Python 3

File details

Details for the file kmock-0.7.tar.gz.

File metadata

  • Download URL: kmock-0.7.tar.gz
  • Upload date:
  • Size: 938.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for kmock-0.7.tar.gz
Algorithm Hash digest
SHA256 45af254980e541207b8b9ae5f90c646a7ea916ecbaebbd27130314e82fe446cc
MD5 a62eb9aa00a451043bdb2f37b37cea65
BLAKE2b-256 5df31b9480494cc80f311d68592b910c057d2d27da02ba87388b5faa02809fab

See more details on using hashes here.

Provenance

The following attestation bundles were made for kmock-0.7.tar.gz:

Publisher: publish.yaml on nolar/kmock

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file kmock-0.7-py3-none-any.whl.

File metadata

  • Download URL: kmock-0.7-py3-none-any.whl
  • Upload date:
  • Size: 77.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for kmock-0.7-py3-none-any.whl
Algorithm Hash digest
SHA256 011be9781f1e4beaffadacfa44205a6f03cd955d81d9ffae84037dfe617a3310
MD5 902952a53248cb0094fd4532f3818d49
BLAKE2b-256 9a290f04d2c1e43a135be592c76449fae51dd300a38c4ca47d0c1255829c371d

See more details on using hashes here.

Provenance

The following attestation bundles were made for kmock-0.7-py3-none-any.whl:

Publisher: publish.yaml on nolar/kmock

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page