How to Mock subprocess Calls in Python with unittest.mock
A Python script that wraps Vagrant up/destroy commands using subprocess, with tests that mock the subprocess call to simulate outputs and errors.
Python code
38 linesimport subprocess
from unittest.mock import patch, Mock
def run_vagrant(action: str) -> str:
result = subprocess.run(
["vagrant", action],
capture_output=True,
text=True,
check=False,
)
return result.stdout.strip()
def vagrant_wrapper(action: str) -> str:
if action not in ("up", "destroy"):
raise ValueError(f"Unsupported action: {action}")
output = run_vagrant(action)
return f"Vagrant {action} done: {output or 'no output'}"
if __name__ == "__main__":
fake_output = "==> default: VM created"
with patch("__main__.run_vagrant", return_value=fake_output) as mock_run:
result = vagrant_wrapper("up")
mock_run.assert_called_once_with("up")
print(result)
with patch("__main__.run_vagrant", return_value="") as mock_run:
result = vagrant_wrapper("destroy")
mock_run.assert_called_once_with("destroy")
print(result)
with patch("__main__.run_vagrant", side_effect=ValueError("Invalid command")):
try:
vagrant_wrapper("invalid")
except ValueError as error:
print(f"Error caught: {error}")
Output
Vagrant up done: ==> default: VM created
Vagrant destroy done: no output
Error caught: Invalid command
How it works
The run_vagrant function uses subprocess.run with capture_output=True and text=True to capture stdout as a string. The wrapper validates the action, calls run_vagrant, and formats the output. patch replaces run_vagrant with a Mock to avoid real subprocess execution, letting you assert calls and control return values. The side_effect parameter lets the mock raise exceptions, simulating failure paths without touching the real system.
Common mistakes
- Forgetting to patch the correct module path (e.g., `__main__` vs the wrapper's module) results in real subprocess calls.
- Using `check=True` in `subprocess.run` raises an exception on non-zero exit, which you may need to handle with `check=False`.
- Not using `text=True` or `capture_output=True` leaves stdout as bytes, complicating string operations.
Variations
- Use `subprocess.check_output` with `stderr` handling for simpler capture.
- Use a context manager `with patch.object(module, 'run_vagrant')` when the function is imported.
Real-world use cases
- Automating infrastructure provisioning in CI pipelines where Vagrant commands run and output is parsed for status.
- Writing unit tests for deployment scripts that invoke VirtualBox or other CLI tools, ensuring deterministic behavior.
- Building a wrapper CLI that manages local development environments and logs Vagrant output for debugging.
Sponsored
More from Automation & scripting
- Aggregate Log Errors Count by Hour in Python easy
- Automate Tweeting New Blog Posts in Python easy
- Automatically Clean Temporary Files from Applications Using Python medium
- Automatically Download the Latest Software Release from GitHub with Python medium
- Automatically Generate Charts from CSV Files with One Command medium
- Automatically Generate Hardware Inventory Reports in Python easy
Keep learning
Related tutorials and quizzes for this topic.