How to Mock subprocess.run for Black Formatter in Python

Use unittest.mock to simulate subprocess.run calls in a Python function that runs the Black formatter, allowing isolated testing without executing external commands.

Medium Python 3.9+ Aug 9, 2026 Modern tooling 15 views 0 copies

Python code

41 lines
Python 3.9+
import subprocess
from unittest.mock import Mock, patch

def run_black_formatter(file_path: str, check_only: bool = False) -> dict:
    """Run black formatter on a file via subprocess."""
    cmd = ["black", "--check" if check_only else "-", file_path]
    result = subprocess.run(cmd, capture_output=True, text=True)
    return {
        "returncode": result.returncode,
        "stdout": result.stdout,
        "stderr": result.stderr,
        "formatted": result.returncode == 0,
    }

if __name__ == "__main__":
    # Mock subprocess.run to simulate black formatter output
    mock_result = Mock()
    mock_result.returncode = 0
    mock_result.stdout = "All done! ✨ 🍰 ✨"
    mock_result.stderr = ""

    with patch("subprocess.run", return_value=mock_result) as mock_run:
        # Test the function
        result = run_black_formatter("example.py", check_only=False)
        
        # Verify subprocess was called with correct arguments
        called_args = mock_run.call_args[0][0]
        print(f"Command called: {called_args}")
        print(f"Formatted successfully: {result['formatted']}")
        print(f"Output: {result['stdout']}")

    # Test with a failure case
    mock_fail = Mock()
    mock_fail.returncode = 1
    mock_fail.stdout = ""
    mock_fail.stderr = "Error: Could not format file"

    with patch("subprocess.run", return_value=mock_fail):
        result = run_black_formatter("bad.py", check_only=True)
        print(f"\nFailure case - Formatted: {result['formatted']}")
        print(f"Error message: {result['stderr']}")

Output

stdout
Command called: ['black', '-', 'example.py']
Formatted successfully: True
Output: All done! ✨ 🍰 ✨

Failure case - Formatted: False
Error message: Error: Could not format file

How it works

The patch context manager replaces subprocess.run with a mock that returns a predefined result, so the function never touches the actual Black executable. Setting returncode, stdout, and stderr on a Mock lets you simulate both success and failure paths. Because the function builds its command list from its arguments, the mock's call_args captures exactly what would be passed to the real subprocess, letting you assert the command construction. This is a common pattern for unit testing functions that wrap external tools, keeping tests fast and deterministic. The approach also works for any CLI tool, not just Black.

Common mistakes

  • Forgetting to patch where `subprocess.run` is used (e.g., patching `black` instead of `subprocess`)
  • Not setting all necessary attributes (returncode, stdout, stderr) on the mock result
  • Using `assert_called_with` without checking the arguments list structure

Variations

  1. Use `unittest.mock.patch` as a decorator on a test method instead of a context manager
  2. Use `subprocess.CompletedProcess` instead of `Mock` for more realistic simulation

Real-world use cases

  • Unit testing a code linting/formatting wrapper in a CI pipeline without slowing down with real formatter execution
  • Testing a pre-commit hook script that runs Black on changed files, mocking to verify file selection logic
  • Validating error handling in an IDE plugin that invokes Black, simulating failures to check user feedback paths

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Modern tooling

Related tutorials and quizzes for this topic.