Skip to main content

High-fidelity command mocking framework

Project description

Command Mock Framework

PyPI version Tests

High-fidelity, deterministic command mocking for Python tests.

Stop mocking subprocess.run with invented strings. Record real command outputs once, store them in version control, and replay them during tests.


The Problem

Testing code that wraps CLI tools (Git, Docker, Kubectl, npm) is painful.

  • Manual mocks are brittle: You guess what git log returns, but you get it wrong.
  • Real commands are slow: Running docker ps in every test kills performance.
  • Integration tests are flaky: Environments change, causing non-deterministic failures.

The Solution

Command Mock Framework sits between your code and subprocess.

  1. Record real command outputs to TOML files.
  2. Replay them instantly in tests.
  3. Match commands flexibly using template placeholders.

Quick Example

1. Record the real behavior:

from command_mock.recorder import CommandMockRecorder

# Run this once to generate 'mocks/git/log/follow.toml'
recorder = CommandMockRecorder("git")
recorder.record_scenario(
    command=["git", "log", "--follow", "--", "{filepath}"],
    scenario_name="basic_history",
    template_vars={"filepath": "app.py"}
)

2. Use it in your test:

def test_get_history(command_mock):
    # Load the mock (command_mock is a pytest fixture)
    mock_fn = command_mock.get_subprocess_mock("log/follow.toml", "basic_history")

    # Patch subprocess.run
    with patch('subprocess.run', side_effect=mock_fn):
        # This executes FAST and returns REAL git output
        # It matches 'git log ... -- app.py' OR 'git log ... -- other.py'
        history = my_git_wrapper.get_history("app.py")

    assert len(history) == 5

Alternatives & Philosophy

The Command Mock Framework occupies a specific niche: it applies the VCR/Record-Replay pattern (popularized by HTTP tools like vcrpy) to subprocess/CLI calls.

Here is how it compares to other tools in the Python ecosystem:

1. The Direct Competitor: pytest-subprocess

This is the most popular modern library for this task. It allows you to define expected commands and outputs inside your test code.

  • pytest-subprocess: Best for Code-Driven testing. Excellent for defining logic flows (e.g., "if command X runs, return exit code 1").
  • command-mock: Best for Data-Driven testing. It separates test data (TOML) from test logic. It shines when output is verbose or structured (like git log or docker inspect) and you want to version-control the exact output snapshot.

2. The Standard Library: unittest.mock

The "vanilla" way that this framework was built to replace.

  • unittest.mock: Often leads to brittle tests. You have to manually invent stdout strings, which means your tests might pass while the real app fails because your invented string wasn't quite right.
  • command-mock: Guarantees fidelity by recording reality first.

3. "Real Execution" Tools (cram, scripttest)

These tools run actual shell commands in a sandbox.

  • cram: Provides 100% realism but is slow and requires a full environment setup (installing git, docker, etc. on the test runner).
  • command-mock: Replays instantly (⚡ Fast) and requires no external tools installed in the CI environment.

4. The HTTP Equivalent: vcrpy

If your CLI tool is primarily a wrapper around an API (e.g., a custom AWS wrapper), you might be mocking the wrong layer.

  • Strategy: Instead of mocking the subprocess call to aws-cli, use the Python SDK (boto3) and use vcrpy to record the HTTP interactions. Use command-mock when you must shell out to a binary.

Summary

Feature command-mock pytest-subprocess unittest.mock cram
Primary Goal High-fidelity Replay Programmatic Logic Basic Mocking Integration Testing
Data Storage External Files (TOML) In Python Code In Python Code Shell Transcript Files
Realism ⭐⭐⭐⭐ (Recorded) ⭐⭐ (Manual) ⭐ (Manual) ⭐⭐⭐⭐⭐ (Real)
Speed ⚡ Fast ⚡ Fast ⚡ Fast 🐢 Slow
Best For Complex outputs Logic flows, exit codes Simple commands End-to-end flows

Conclusion: If you need to mock complex tools like Git, Docker, or Kubectl where the stdout is verbose and structured, command-mock is the best fit because managing those massive strings inside Python code is messy and error-prone.


Installation

pip install orgecc-command-mock

Usage Guide

1. Setup

Add the pytest plugin to your conftest.py:

# tests/conftest.py
import pytest
from pathlib import Path
from command_mock.recorder import CommandMockRecorder
from command_mock.player import CommandMockPlayer

@pytest.fixture
def command_mock(request):
    """Fixture that switches between Player (default) and Recorder."""
    # Point this to where you want to store your TOML files
    fixtures_root = Path(__file__).parent

    if request.config.getoption("--regenerate-mocks"):
        return CommandMockRecorder("git", fixtures_root=fixtures_root)
    else:
        return CommandMockPlayer("git", fixtures_root=fixtures_root)

def pytest_addoption(parser):
    parser.addoption("--regenerate-mocks", action="store_true", help="Record new mocks")

2. Recording Mocks

Create a script (e.g., tests/generate_mocks.py) to generate your test data. This ensures your mocks are reproducible.

from command_mock.recorder import CommandMockRecorder

def generate():
    recorder = CommandMockRecorder("git")

    # You can even use setup scripts to create real repo states!
    repo = recorder.create_test_repo("scripts/setup_repo.sh")

    recorder.record_scenario(
        command=["git", "status"],
        scenario_name="clean_state",
        output_path="status/clean.toml",
        repo_path=repo
    )

if __name__ == "__main__":
    generate()

3. Flexible Matching

This is the framework's superpower. You don't need a mock for every single argument variation. Use placeholders in your templates.

Embedded Placeholders:

# Template in TOML:
command = ["git", "log", "--grep={term}"]

# Matches all of these in tests:
["git", "log", "--grep=fix"]
["git", "log", "--grep=feat"]

Standalone Placeholders:

# Template in TOML:
command = ["git", "add", "{filepath}"]

# Matches all of these in tests:
["git", "add", "src/main.py"]
["git", "add", "tests/test_core.py"]

Dynamic Flag Stripping: The player automatically handles flags that change every run (like --since="1 hour ago"), matching the core command while ignoring the dynamic parts.

Development

This project uses a Makefile for all development tasks.

make install      # Install dev dependencies
make test         # Run tests
make format       # Format code (Black/Ruff)
make ci           # Run full CI suite locally

For detailed contribution guidelines, see CLAUDE.md.

License

Apache 2.0

Project details


Download files

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

Source Distribution

orgecc_command_mock-0.1.4.tar.gz (29.4 kB view details)

Uploaded Source

Built Distribution

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

orgecc_command_mock-0.1.4-py3-none-any.whl (13.0 kB view details)

Uploaded Python 3

File details

Details for the file orgecc_command_mock-0.1.4.tar.gz.

File metadata

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

File hashes

Hashes for orgecc_command_mock-0.1.4.tar.gz
Algorithm Hash digest
SHA256 6a88d08da81d5665cec43a73fb74591b7a543266e702ae69ce937e36c5debd57
MD5 bbd87b23b10a29aabca19d5d39b81ab2
BLAKE2b-256 1a99de5fd07bd2693492fd4c50ff2cdf1e64bf5f5831c23703f11edb64930441

See more details on using hashes here.

Provenance

The following attestation bundles were made for orgecc_command_mock-0.1.4.tar.gz:

Publisher: release.yml on elifarley/command-mock

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

File details

Details for the file orgecc_command_mock-0.1.4-py3-none-any.whl.

File metadata

File hashes

Hashes for orgecc_command_mock-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 9533de4ec200202532c53d710718b22b8f0557cead192cffd1bebd23b99e7a5c
MD5 994e9fec7b9f371b4ac042fe9aef0a01
BLAKE2b-256 38ce0e422fc3aeb7cea734e531b518c97ebfc83ac362ec5fbe957d9495e481af

See more details on using hashes here.

Provenance

The following attestation bundles were made for orgecc_command_mock-0.1.4-py3-none-any.whl:

Publisher: release.yml on elifarley/command-mock

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