Search code examples
pythonunit-testingmockingioerror

Unittest for IOError exception handling


Given this code:

 try:
        #do something
 except IOError as message:
        logging.error(message)
        raise message

I want to test the exception handling part in order to have full coverage. In the unittest I've tried with:

        with patch(new=Mock(side_effect=IOError(errno.EIO))):
            self.assertRaises(IOError)

but it doesnt work. Is this approach correct?


Solution

  • Actually you need to start the Mock so that the side_effectstarts, for example the following:

    class Test(unittest.TestCase):
    
        def test(self):
            mock = m.Mock()
            mock.side_effect = Exception("Big badaboum")
            self.assertRaises(Exception, mock)
    

    self.assertRaises can take a callable as second argument, making it equivalent to:

    class Test(unittest.TestCase):
        def test(self):
            mock = m.Mock()
            mock.side_effect = Exception("Big badaboum")
            with self.assertRaises(Exception):
                mock()
    

    And if you want to use it in a test with patch, you can do the following:

    import unittest.mock as m
    import unittest
    
    def raise_error():
        try:
            print("Hello") #placeholder for the try clause
        except Exception as e:
            print(e) #placeholder for the exceptclause
    
    class Test(unittest.TestCase):
        @m.patch("__main__.raise_error", side_effect=Exception("Big badaboum")) #replace  __main__ by the name of the module with your function
        def test(self, mock):
            with self.assertRaises(Exception):
                mock()
    
    unittest.main()
    

    Edit: And to test the raise of an error inside an except block you need to mock a function call inside the try block you wrote, for instance:

    import unittest.mock as m
    import unittest
    
    def do_sthing():
        print("Hello")
    
    def raise_error():
        try:
            do_sthing() #this call can be mocked to raise an IOError
        except IOError as e:
            print(e.strerror)
            raise ValueError("Another one")
    
    class Test(unittest.TestCase):
        def test(self):
            with m.patch("__main__.do_sthing", side_effect=IOError("IOError")):
                self.assertRaises(ValueError, raise_error)
    
    
    unittest.main()
    

    You can use the decorator syntax as well (just putting the test above rewritten to spare some CPU cycle):

    class Test(unittest.TestCase):
        @m.patch("__main__.do_sthing",side_effect=IOError("IOError"))
        def test(self, mock):
            self.assertRaises(ValueError, raise_error)