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.

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

Python code

32 lines
Python 3.9+
import 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

stdout
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

  1. Use `parser.parse_known_args` to handle unknown arguments gracefully
  2. 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

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.