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')
As noted in my updates to my question, I had to modify dF‘s answer to:
…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.]