5

I'm writing test cases for argparse implementation. I intend to test '-h' feature. The following code does it. But it also outputs the usage for the script. Is there a way to suppress that?

self.assertRaises(SystemExit, arg_parse_obj.parse_known_args, ['-h'])

Also, can we check for the exception number thrown? For example '-h' throws SystemExit: 0, while invalid or insufficient args throw SystemExit: 2. Is there a way to check the numeric code?

Anshu Kumar
  • 606
  • 6
  • 18

3 Answers3

18

When testing for exception codes, use self.assertRaises() as a context manager; this gives you access to the raised exception, letting you test the .code attribute:

with self.assertRaises(SystemExit) as cm:
    arg_parse_obj.parse_known_args(['-h'])

self.assertEqual(cm.exception.code, 0)

To 'suppress' or test the output, you'll have to capture either sys.stdout or sys.stderr, depending on the argparse output (help text goes to stdout). You could use a context manager for that:

from contextlib import contextmanager
from StringIO import StringIO

@contextmanager
def capture_sys_output():
    capture_out, capture_err = StringIO(), StringIO()
    current_out, current_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = capture_out, capture_err
        yield capture_out, capture_err
    finally:
        sys.stdout, sys.stderr = current_out, current_err

and use these as:

with self.assertRaises(SystemExit) as cm:
    with capture_sys_output() as (stdout, stderr):
        arg_parse_obj.parse_known_args(['-h'])

self.assertEqual(cm.exception.code, 0)

self.assertEqual(stderr.getvalue(), '')
self.assertEqual(stdout.getvalue(), 'Some help value printed')

I nested the context managers here, but in Python 2.7 and newer you can also combine them into one line; this tends to get beyond the recommended 79 character limit in a hurry though.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Ouch, I thought that `with` would allow continuation-parenthesis but it doesn't, so you cannot do things like `with (really_long_expression as a, really_really_long_expression as b): suite`. – Bakuriu Sep 06 '13 at 07:22
  • I hate using backslash for continuation. I hope in the future they will allow parenthesis. The only problem I can see is that allowing them the syntax `with (, , ...):`(without using `as`) will be ambiguous because it could either mean to use nested context managers, or to use the tuple itself as a context manager(which doesn't make sense...). – Bakuriu Sep 06 '13 at 07:34
  • I copy and pasted this code in `3.5` and `stdout` is empty while the help message is printed in `stderr` – raphael Dec 01 '16 at 15:44
  • @raphael: help, by default, goes to stdout (see the [`print_help()` function](https://hg.python.org/cpython/file/3.5/Lib/argparse.py#l2355)). In case of an error, `print_usage()` is used instead and [given `stdout` as the output to write to](https://hg.python.org/cpython/file/3.5/Lib/argparse.py#l2383). – Martijn Pieters Dec 01 '16 at 16:27
  • @MartijnPieters ah my b. I was basing myself on your code but trying to test errors and missed that distinction – raphael Dec 01 '16 at 16:29
  • Did you mean to say `the_exception.code` or `exit_exception.code`? Or for that matter, why not just `cm.exception.code`? :) – fbicknel Apr 02 '19 at 15:14
  • @fbicknel: yes, and no idea, it's been too long, so I corrected for both. Thanks! – Martijn Pieters Apr 02 '19 at 15:59
2

Mock could do this, allowing you the same functionality as Martijn Pieters' answer but without having to write your own function:

from unittest.mock import MagicMock, patch

argparse_mock = MagicMock()
with patch('argparse.ArgumentParser._print_message', argparse_mock):
    with self.assertRaises(SystemExit) as cm:
        arg_parse_obj.parse_known_args(['-h'])

patch also works as a decorator. If you have several instances where the argparse printing needs to be suppressed, you can do it as a decorator and avoid using a bunch of nested with statements.

Levi Noecker
  • 3,142
  • 1
  • 15
  • 30
0

Some variations on using ['-h'] include:

parser.print_help()  # formats the help, prints it and exits
parser.format_help() # format the help without printing or exit
parser.exit  # can be modified to not exit, such as for a parser subclass
parser.error # default print usage and call parser.exit

These are part of the public API.

The argparse test file (test_argparse.py) also gives ideas on how to test things. For many tests it uses an ArgumentParser subclass that has its own error method.

hpaulj
  • 221,503
  • 14
  • 230
  • 353