How to mock argparse nested subparsers in Python
Build an argparse parser with nested subparsers and test it using unittest.mock.patch for sys.argv and sys.stdout.
Python code
32 linesimport argparse
from unittest.mock import patch
from io import StringIO
def build_parser():
parser = argparse.ArgumentParser(prog="app")
subparsers = parser.add_subparsers(dest="command", required=True)
# Outer subparser
outer = subparsers.add_parser("outer")
outer_sub = outer.add_subparsers(dest="subcommand", required=True)
# Inner subparser
inner = outer_sub.add_parser("inner")
inner.add_argument("--value", type=int, default=0)
return parser
def run(args):
parser = build_parser()
parsed = parser.parse_args(args)
if parsed.command == "outer" and parsed.subcommand == "inner":
return f"inner value: {parsed.value}"
return "unknown"
if __name__ == "__main__":
# Test with actual argparse
print(run(["outer", "inner", "--value", 42]))
# Demonstrate mocking for testing
with patch("sys.argv", ["app", "outer", "inner", "--value", 7]):
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
print(run([]))
Output
inner value: 42
inner value: 7
How it works
This code defines a parser with an outer and inner subparser, allowing a nested command structure. The dest parameters capture the selected commands, and the required=True ensures they are provided. run parses arguments and returns a formatted string based on the command hierarchy. The mocking uses unittest.mock.patch to replace sys.argv and sys.stdout with controlled values, enabling deterministic testing without user input.
Common mistakes
- Forgetting `required=True` on outer subparsers can cause a runtime error when no command is given
- Not setting `dest` leads to ambiguous attribute access on the parsed namespace
- Patching `sys.argv` without restoring can affect other tests, so use it inside a `with` block
Variations
- Use `parser.parse_known_args` to handle unknown arguments gracefully
- Implement the same logic with `typing.ArgumentParser` for better type hints
Real-world use cases
- CLI tools with nested command groups like git (e.g., git remote add) where subcommands are conditional.
- Testing command-line entry points in CI without spawning subprocesses, mocking sys.argv for deterministic tests.
- Creating modular script interfaces that group related actions under parent commands for user-friendly navigation.
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.