Write pytest tests for argparse

Writing unit tests for Python code that uses argparse can be non-trivial.

Call main with optional arguments

One way suggested by Simon Willison was to make main() function take optional arguments:

parser = argparse.ArgumentParser()
parser.add_argument(...)

def main(args=None):
    parsed_args = parser.parse_args(args)

This makes it easy to just test main() function by calling it with different arguments:

@pytest.mark.parametrize("option", ("-h", "--help"))
def test_help(capsys, option):
    try:
        main([option])
    ...

Patch sys.argv

It’s also possible to patch the sys.argv with the mock arguments for testing:

def command():
    parser = argparse.ArgumentParser()
    args = parser.parse_args()
    ...

def test_command():
    with unittest.mock.patch('sys.argv'. ['arg1', 'arg2'])
        command()
        ...

Patch argparse directly

In some rare scenarios, argument parser may be at the global scope inside a module file:

parser = argparse.ArgumentParser()
args = parser.parse_args()

class MyClass:
    def __init__(self):
        self.foo = args.foo

In the above case, when the class is imported, the parser will be executed. This makes the unit tests tricky simply because the argument parsing process happens at module import level rather than inside a function call.

A straightforward solution is to patch the ArgumentParser or the args directly:

mock_args = {"foo": "bar"}

@unittest.mock.patch('module.args', argparse.Namespace(**mock_args))
def test_class():
    obj = MyClass()
    ...

The same can also be achieved by patch the argparse.ArgumentParser.parse_args function. See stackoverflow answer .

@mock.patch('argparse.ArgumentParser.parse_args',
            return_value=argparse.Namespace(kwarg1=value, kwarg2=value))
def test_command(mock_args):
    pass