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