Search code examples
pythonexceptionmockingpytestoserror

Pytest: unit test to reproduce OSError


How do I reproduce OSError so that I can know that my exception works?

def write_file():
    try: 
        with open('file.txt', "w+") as f:
            f.write("sth")
        f.close()
    except OSError as e:
        logging.error("OSError occured")

I want to write unit test for the funtion write_file() using pytest. How do I mock the OSError?


Solution

  • You'd have to mock out the open() call. You could do so with standard library unittest.mock.patch() function, or with the pytest monkeypatch fixture; I personally prefer to use the standard library here:

    import pytest
    import logging
    from unittest import mock
    from module_under_test import write_file
    
    def test_write_file_error(caplog):
        caplog.clear()
        with mock.patch("module_under_test.open") as mock_open:
            mock_open.side_effect = OSError
            write_file()
    
        assert caplog.record_tuples == [("root", logging.ERROR, "OSError occured")]
    

    The mock.patch() context manager setup places a mocked open object in the module_under_test global namespace, masking the built-in open() function. Setting the side_effect attribute to an exception ensures that calling the mocked object will raise that exception.

    Mocking out open() is far easier than trying to create the exact filesystem circumstances in which the built-in open() function would raise an exception. Moreover, you are testing how your own code is handling OSError correctly, not if open() is working as designed.

    Some side notes:

    • There is no need to call f.close(); you are using the open file as a context manager (with ... as f:) and so it is closed automatically, whatever happens in the with block.
    • The correct spelling is occurred (double r) :-)
    • Don't use except OSError as e: when you don't intent to use the e reference to the exception; drop the as e part.
    • If you use the logging.exception() function, then the exception and the full traceback are captured in the log, as an ERROR level message.
    def write_file():
        try: 
            with open('file.txt', "w+") as f:
                f.write("sth")
        except OSError:
            logging.exception("Failed to write to file.txt")