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?

Asked By: pipppoo

||

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")
Answered By: phd

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)
Answered By: hpaulj

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
Answered By: Giorgos Myrianthous

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)

Answered By: bluenote10
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.