How do I mock the Python method OptionParser.error(), which does a sys.exit()?
Asked Answered
D

4

15

I'm trying to unit test some code that looks like this:

def main():
    parser = optparse.OptionParser(description='This tool is cool', prog='cool-tool')
    parser.add_option('--foo', action='store', help='The foo option is self-explanatory')
    options, arguments = parser.parse_args()
    if not options.foo:
        parser.error('--foo option is required')
    print "Your foo is %s." % options.foo
    return 0

if __name__ == '__main__':
   sys.exit(main())

With code that looks like this:

@patch('optparse.OptionParser')
def test_main_with_missing_p4clientsdir_option(self, mock_optionparser):
    #
    # setup
    #
    optionparser_mock = Mock()
    mock_optionparser.return_value = optionparser_mock
    options_stub = Mock()
    options_stub.foo = None
    optionparser_mock.parse_args.return_value = (options_stub, sentinel.arguments)
    def parser_error_mock(message):
        self.assertEquals(message, '--foo option is required')
        sys.exit(2)
    optionparser_mock.error = parser_error_mock

    #
    # exercise & verify
    #
    self.assertEquals(sut.main(), 2)

I'm using Michael Foord's Mock, and nose to run the tests.

When I run the test, I get:

  File "/Users/dspitzer/Programming/Python/test-optparse-error/tests/sut_tests.py", line 27, in parser_error_mock
    sys.exit(2)
SystemExit: 2

----------------------------------------------------------------------
Ran 1 test in 0.012s

FAILED (errors=1)

The problem is that OptionParser.error does a sys.exit(2), and so main() naturally relies on that. But nose or unittest detects the (expected) sys.exit(2) and fails the test.

I can make the test pass by adding "return 2" under the parser.error() call in main() and removing the sys.exit() call from parser_error_mock(), but I find it distasteful to modify the code under test to allow a test to pass. Is there a better solution?

Update: df's answer works, although the correct call is "self.assertRaises(SystemExit, sut.main)".

Which means the test passes whatever the number is in the sys.exit() in parser_error_mock(). Is there any way to test for the exit code?

BTW, the test is more robust if I add:

self.assertEquals(optionparser_mock.method_calls, [('add_option', ('--foo',), {'action': 'store', 'help': 'The foo option is self-explanatory'}), ('parse_args', (), {})])

at the end.

Update 2: I can test for the exit code by replacing "self.assertRaises(SystemExit, sut.main)" with:

try:
    sut.main()
except SystemExit, e:
    self.assertEquals(type(e), type(SystemExit()))
    self.assertEquals(e.code, 2)
except Exception, e:
    self.fail('unexpected exception: %s' % e)
else:
    self.fail('SystemExit exception expected')
Dithyramb answered 8/1, 2009 at 23:37 Comment(1)
The first assertEquals on your Update 2 is unnecessary, since the "except" line above it will only catch SystemExit exceptions.Banded
D
1

As noted in my updates to my question, I had to modify dF's answer to:

self.assertRaises(SystemExit, sut.main)

...and I came up with a few longer snippet to test for the exit code.

[Note: I accepted my own answer, but I will delete this answer and accept dF's if he updates his.]

Dithyramb answered 18/1, 2009 at 17:48 Comment(0)
G
12

Will this work instead of assertEquals?

self.assertRaises(SystemExit, sut.main, 2)

This should catch the SystemExit exception and prevent the script from terminating.

Groscr answered 9/1, 2009 at 0:24 Comment(1)
I've "unaccepted" your answer, since it's not quite correct. But since you gave me the insight that led me to the answer (see the updates to the question), I'll give you a couple days to edit your answer and then I'll accept.Dithyramb
D
1

As noted in my updates to my question, I had to modify dF's answer to:

self.assertRaises(SystemExit, sut.main)

...and I came up with a few longer snippet to test for the exit code.

[Note: I accepted my own answer, but I will delete this answer and accept dF's if he updates his.]

Dithyramb answered 18/1, 2009 at 17:48 Comment(0)
T
0

Probably this question contains some new information:

Java: How to test methods that call System.exit()?

Tatia answered 9/1, 2009 at 7:9 Comment(1)
I read it quickly and while it's the same problem (in Java), I didn't see anything applicable here (to Python). Do you?Dithyramb
O
0

Add @raises to your test function. I.e:

from nose.tools import raises

@raises(SystemExit)
@patch('optparse.OptionParser')
def test_main_with_missing_p4clientsdir_option(self, mock_optionparser):
    # Setup
    ...
    sut.main()
Oleate answered 3/12, 2012 at 17:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.