From afc98d4e4eab7d66b09483fb3da1ac30e766d026 Mon Sep 17 00:00:00 2001 From: Michael Foord <michael@voidspace.org.uk> Date: Tue, 13 Mar 2012 16:38:50 -0700 Subject: [PATCH] Adding mock_open helper --- docs/changelog.txt | 3 +++ mock.py | 39 +++++++++++++++++++++++++++++++++-- tests/testhelpers.py | 48 ++++++++++++++++++++++++++++++++++++++++++-- tests/testmock.py | 12 +++++++++++ 4 files changed, 98 insertions(+), 4 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 90c1ed3..015eda0 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -10,6 +10,9 @@ CHANGELOG The standard library version! * `mocksignature`, along with the `mocksignature` argument to `patch`, removed +* Support for deleting attributes (accessing deleted attributes will raise an + `AttributeError`) +* Added the `mock_open` helper function for mocking open as a context manager 2012/02/13 Version 0.8.0 diff --git a/mock.py b/mock.py index 640203b..b6ef6f2 100644 --- a/mock.py +++ b/mock.py @@ -25,6 +25,7 @@ __all__ = ( 'FILTER_DIR', 'NonCallableMock', 'NonCallableMagicMock', + 'mock_open', ) @@ -335,6 +336,8 @@ class _Sentinel(object): sentinel = _Sentinel() DEFAULT = sentinel.DEFAULT +_missing = sentinel.MISSING +_deleted = sentinel.DELETED class OldStyleClass: @@ -630,7 +633,9 @@ class NonCallableMock(Base): raise AttributeError(name) result = self._mock_children.get(name) - if result is None: + if result is _deleted: + raise AttributeError(name) + elif result is None: wraps = None if self._mock_wraps is not None: # XXXX should we get the attribute without triggering code @@ -757,7 +762,16 @@ class NonCallableMock(Base): # not set on the instance itself return - return object.__delattr__(self, name) + if name in self.__dict__: + object.__delattr__(self, name) + + obj = self._mock_children.get(name, _missing) + if obj is _deleted: + raise AttributeError(name) + if obj is not _missing: + del self._mock_children[name] + self._mock_children[name] = _deleted + def _format_mock_call_signature(self, args, kwargs): @@ -2201,3 +2215,24 @@ FunctionAttributes = set([ 'func_globals', 'func_name', ]) + +if inPy3k: + import _io + file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) +else: + file_spec = file + + +def mock_open(mock=None, read_data=None): + if mock is None: + mock = MagicMock(spec=file_spec) + + handle = MagicMock(spec=file_spec) + handle.write.return_value = None + handle.__enter__.return_value = handle + + if read_data is not None: + handle.read.return_value = read_data + + mock.return_value = handle + return mock diff --git a/tests/testhelpers.py b/tests/testhelpers.py index 1b3dd2f..1b04c88 100644 --- a/tests/testhelpers.py +++ b/tests/testhelpers.py @@ -5,8 +5,8 @@ from tests.support import unittest2, inPy3k from mock import ( - call, _Call, create_autospec, - MagicMock, Mock, ANY, _CallList + call, _Call, create_autospec, MagicMock, + Mock, ANY, _CallList, mock_open, patch ) from datetime import datetime @@ -852,5 +852,49 @@ class TestCallList(unittest2.TestCase): self.assertEqual(str(mock.mock_calls), expected) + +class TestMockOpen(unittest2.TestCase): + + def test_mock_open(self): + mock = mock_open() + with patch('%s.open' % __name__, mock, create=True) as patched: + self.assertIs(patched, mock) + open('foo') + + mock.assert_called_once_with('foo') + + + def test_mock_open_context_manager(self): + mock = mock_open() + handle = mock.return_value + with patch('%s.open' % __name__, mock, create=True): + with open('foo') as f: + f.read() + + expected_calls = [call('foo'), call().__enter__(), call().read(), + call().__exit__(None, None, None)] + self.assertEqual(mock.mock_calls, expected_calls) + self.assertIs(f, handle) + + + def test_explicit_mock(self): + mock = MagicMock() + mock_open(mock) + + with patch('%s.open' % __name__, mock, create=True) as patched: + self.assertIs(patched, mock) + open('foo') + + mock.assert_called_once_with('foo') + + + def test_read_data(self): + mock = mock_open(read_data='foo') + with patch('%s.open' % __name__, mock, create=True): + h = open('bar') + result = h.read() + + self.assertEqual(result, 'foo') + if __name__ == '__main__': unittest2.main() diff --git a/tests/testmock.py b/tests/testmock.py index 886f9bf..9e9c4a1 100644 --- a/tests/testmock.py +++ b/tests/testmock.py @@ -1291,5 +1291,17 @@ class MockTest(unittest2.TestCase): self.assertEqual(m.mock_calls, call().foo().call_list()) + def test_attribute_deletion(self): + for mock in Mock(), MagicMock(): + self.assertTrue(hasattr(mock, 'm')) + + del mock.m + self.assertFalse(hasattr(mock, 'm')) + + del mock.f + self.assertFalse(hasattr(mock, 'f')) + self.assertRaises(AttributeError, getattr, mock, 'f') + + if __name__ == '__main__': unittest2.main() -- GitLab