19

I'm using python-mock to mock out a file open call. I would like to be able to pass in fake data this way, so I can verify that read() is being called as well as using test data without hitting the filesystem on tests.

Here's what I've got so far:

file_mock = MagicMock(spec=file)
file_mock.read.return_value = 'test'

with patch('__builtin__.open', create=True) as mock_open:
    mock_open.return_value = file_mock

    with open('x') as f:
        print f.read()

The output of this is <mock.Mock object at 0x8f4aaec> intead of 'test' as I would assume. What am I doing wrong in constructing this mock?

Edit:

Looks like this:

with open('x') as f:
     f.read()

and this:

f = open('x')
f.read()

are different objects. Using the mock as a context manager makes it return a new Mock, whereas calling it directly returns whatever I've defined in mock_open.return_value. Any ideas?

Brian Hicks
  • 6,213
  • 8
  • 51
  • 77

3 Answers3

31

In Python 3 the pattern is simply:

>>> import unittest.mock as um
>>> with um.patch('builtins.open', um.mock_open(read_data='test')):
...     with open('/dev/null') as f:
...         print(f.read())
...
test
>>>

(Yes, you can even mock /dev/null to return file contents.)

tbc0
  • 1,563
  • 1
  • 17
  • 21
14

This sounds like a good use-case for a StringIO object that already implements the file interface. Maybe you can make a file_mock = MagicMock(spec=file, wraps=StringIO('test')). Or you could just have your function accept a file-like object and pass it a StringIO instead of a real file, avoiding the need for ugly monkey-patching.

Have you looked the mock documentation?

http://www.voidspace.org.uk/python/mock/compare.html#mocking-the-builtin-open-used-as-a-context-manager

vidstige
  • 12,492
  • 9
  • 66
  • 110
Michael Merickel
  • 23,153
  • 3
  • 54
  • 70
  • I tried messing around some with this. It appears that mock_open is return a different object when used in a context manager versus being called directly. I've updated my question. Any ideas? – Brian Hicks Jan 05 '12 at 19:24
  • I read the top half of that page, but skipped the bottom half for some unknown reason. Herp and derp. – Brian Hicks Jan 05 '12 at 20:12
  • 3
    If the website would be still up to this date, that would be helpful. Now the answer becomes a bit useless. – Vad1mo Jan 19 '22 at 22:29
  • Please include imports into your answer as well. Otherwise it is difficult to reproduce – Natan Aug 29 '22 at 06:44
2

building on @tbc0 answer, to support Python 2 and 3 (multi-version tests are helpful to port 2 to 3):

import sys
module_ = "builtins"
module_ = module_ if module_ in sys.modules else '__builtin__'

try:
    import unittest.mock as mock
except (ImportError,) as e:
    import mock 

with mock.patch('%s.open' % module_, mock.mock_open(read_data='test')):
    with open('/dev/null') as f:
        print(f.read())
JL Peyret
  • 10,917
  • 2
  • 54
  • 73
  • You can use the six library to import `builtins` https://six.readthedocs.io/index.html?highlight=builtins#module-six.moves – Godfrey Jun 23 '20 at 16:31