Search code examples
pythonunit-testingmockingpython-unittest

How do I mock a file open for a specific path in python?


So I know that in my unit test I can mock a context manager open(), i.e.:

with open('file_path', 'r') as stats:

mocked with

with mock.patch('builtins.open', mock.mock_open(read_data=mock_json)):

but is there a way for me to only mock it for a specific file path? Or maybe some other way to ensure that the context manager gets called with the correct path in a unit test?


Solution

  • To mock open only for a specific path, you have to provide your own mock object that handles open differently, depending on the path. Assuming we have some function:

    def do_open(path):
        with open(path, "r") as f:
            return f.read()
    

    where open shall be mocked to return a file with the content "bar" if path is "foo", but otherwise just work as usual, you could do something like this:

    from unittest import mock
    from my_module.do_open import do_open
    
    builtin_open = open  # save the unpatched version
    
    def mock_open(*args, **kwargs):
        if args[0] == "foo":
            # mocked open for path "foo"
            return mock.mock_open(read_data="bar")(*args, **kwargs)
        # unpatched version for every other path
        return builtin_open(*args, **kwargs)
    
    @mock.patch("builtins.open", mock_open)
    def test_open():
        assert do_open("foo") == "bar"
        assert do_open(__file__) != "bar"
    

    If you don't want to save the original open in a global variable, you could also wrap that into a class:

    class MockOpen:
        builtin_open = open
    
        def open(self, *args, **kwargs):
            if args[0] == "foo":
                return mock.mock_open(read_data="bar")(*args, **kwargs)
            return self.builtin_open(*args, **kwargs)
    
    @mock.patch("builtins.open", MockOpen().open)
    def test_open():
        ...