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.
Python code
41 linesimport 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
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
- Use `unittest.mock.patch` as a decorator on a test method instead of a context manager
- 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
More from Modern tooling
- Build a Recipe Runner Mock in Python easy
- Build a Textual TUI App Skeleton in Python easy
- Configure ruff linter rules in pyproject.toml with Python easy
- Data Conversion Helper Functions in Python easy
- How to Bind and Mock structlog Context in Python medium
- How to Build a Chainable Filter Helper in Python easy
Keep learning
Related tutorials and quizzes for this topic.