Check argparse.ArgumentTypeError
Question:
I want to use pytest
to check if the argparse.ArgumentTypeError
exception is raised for an incorrect argument:
import argparse
import os
import pytest
def main(argsIn):
def configFile_validation(configFile):
if not os.path.exists(configFile):
msg = 'Configuration file "{}" not found!'.format(configFile)
raise argparse.ArgumentTypeError(msg)
return configFile
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--configFile', help='Path to configuration file', dest='configFile', required=True, type=configFile_validation)
args = parser.parse_args(argsIn)
def test_non_existing_config_file():
with pytest.raises(argparse.ArgumentTypeError):
main(['--configFile', 'non_existing_config_file.json'])
However, running pytest
says During handling of the above exception, another exception occurred:
and consequently the test fails. What am I doing wrong?
Answers:
The problem is that if argument’s type converter raises exception ArgumentTypeError
agrparse
exits with error code 2, and exiting means raising builtin exception SystemExit
. So you have to catch that exception and verify that the original exception is of a proper type:
def test_non_existing_config_file():
try:
main(['--configFile', 'non_existing_config_file.json'])
except SystemExit as e:
assert isinstance(e.__context__, argparse.ArgumentError)
else:
raise ValueError("Exception not raised")
Here’s the ArgumentTypeError
test in the test_argparse.py
file (found in the development repository)
ErrorRaisingAgumentParser
is a subclass defined at the start of the file, which redefines the parser.error
method, so it doesn’t exit, and puts the error message on stderr
. That part’s a bit complicated.
Because of the redirection I described the comment, it can’t directly test for ArgumentTypeError
. Instead it has to test for its message.
# =======================
# ArgumentTypeError tests
# =======================
class TestArgumentTypeError(TestCase):
def test_argument_type_error(self):
def spam(string):
raise argparse.ArgumentTypeError('spam!')
parser = ErrorRaisingArgumentParser(prog='PROG', add_help=False)
parser.add_argument('x', type=spam)
with self.assertRaises(ArgumentParserError) as cm:
parser.parse_args(['XXX'])
self.assertEqual('usage: PROG xnPROG: error: argument x: spam!n',
cm.exception.stderr)
Using pytest
you can do the following in order to check that argparse.ArugmentError
is raised. Additionally, you can check the error message.
with pytest.raises(SystemExit) as e:
main(['--configFile', 'non_existing_config_file.json'])
assert isinstance(e.value.__context__, argparse.ArgumentError)
assert 'expected err msg' in e.value.__context__.message
Inspired by @Giorgos’s answer, here is a small context manager that makes the message extraction a bit more re-usable. I’m defining the following in a common place:
import argparse
import pytest
from typing import Generator, Optional
class ArgparseErrorWrapper:
def __init__(self):
self._error: Optional[argparse.ArgumentError] = None
@property
def error(self):
assert self._error is not None
return self._error
@error.setter
def error(self, value: object):
assert isinstance(value, argparse.ArgumentError)
self._error = value
@contextmanager
def argparse_error() -> Generator[ArgparseErrorWrapper, None, None]:
wrapper = ArgparseErrorWrapper()
with pytest.raises(SystemExit) as e:
yield wrapper
wrapper.error = e.value.__context__
This allows to test for parser errors concisely:
def test_something():
with argparse_error() as e:
# some parse_args call here
...
assert "Expected error message" == str(e.error)
I want to use pytest
to check if the argparse.ArgumentTypeError
exception is raised for an incorrect argument:
import argparse
import os
import pytest
def main(argsIn):
def configFile_validation(configFile):
if not os.path.exists(configFile):
msg = 'Configuration file "{}" not found!'.format(configFile)
raise argparse.ArgumentTypeError(msg)
return configFile
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--configFile', help='Path to configuration file', dest='configFile', required=True, type=configFile_validation)
args = parser.parse_args(argsIn)
def test_non_existing_config_file():
with pytest.raises(argparse.ArgumentTypeError):
main(['--configFile', 'non_existing_config_file.json'])
However, running pytest
says During handling of the above exception, another exception occurred:
and consequently the test fails. What am I doing wrong?
The problem is that if argument’s type converter raises exception ArgumentTypeError
agrparse
exits with error code 2, and exiting means raising builtin exception SystemExit
. So you have to catch that exception and verify that the original exception is of a proper type:
def test_non_existing_config_file():
try:
main(['--configFile', 'non_existing_config_file.json'])
except SystemExit as e:
assert isinstance(e.__context__, argparse.ArgumentError)
else:
raise ValueError("Exception not raised")
Here’s the ArgumentTypeError
test in the test_argparse.py
file (found in the development repository)
ErrorRaisingAgumentParser
is a subclass defined at the start of the file, which redefines the parser.error
method, so it doesn’t exit, and puts the error message on stderr
. That part’s a bit complicated.
Because of the redirection I described the comment, it can’t directly test for ArgumentTypeError
. Instead it has to test for its message.
# =======================
# ArgumentTypeError tests
# =======================
class TestArgumentTypeError(TestCase):
def test_argument_type_error(self):
def spam(string):
raise argparse.ArgumentTypeError('spam!')
parser = ErrorRaisingArgumentParser(prog='PROG', add_help=False)
parser.add_argument('x', type=spam)
with self.assertRaises(ArgumentParserError) as cm:
parser.parse_args(['XXX'])
self.assertEqual('usage: PROG xnPROG: error: argument x: spam!n',
cm.exception.stderr)
Using pytest
you can do the following in order to check that argparse.ArugmentError
is raised. Additionally, you can check the error message.
with pytest.raises(SystemExit) as e:
main(['--configFile', 'non_existing_config_file.json'])
assert isinstance(e.value.__context__, argparse.ArgumentError)
assert 'expected err msg' in e.value.__context__.message
Inspired by @Giorgos’s answer, here is a small context manager that makes the message extraction a bit more re-usable. I’m defining the following in a common place:
import argparse
import pytest
from typing import Generator, Optional
class ArgparseErrorWrapper:
def __init__(self):
self._error: Optional[argparse.ArgumentError] = None
@property
def error(self):
assert self._error is not None
return self._error
@error.setter
def error(self, value: object):
assert isinstance(value, argparse.ArgumentError)
self._error = value
@contextmanager
def argparse_error() -> Generator[ArgparseErrorWrapper, None, None]:
wrapper = ArgparseErrorWrapper()
with pytest.raises(SystemExit) as e:
yield wrapper
wrapper.error = e.value.__context__
This allows to test for parser errors concisely:
def test_something():
with argparse_error() as e:
# some parse_args call here
...
assert "Expected error message" == str(e.error)