Skip to content

Instantly share code, notes, and snippets.

@wware
Last active May 18, 2021 15:18
Show Gist options
  • Save wware/5367159 to your computer and use it in GitHub Desktop.
Save wware/5367159 to your computer and use it in GitHub Desktop.
I find mock confusing and mystical. Here is some clarification.
"""
Mock is confusing as hell. Document some of it.
http://www.voidspace.org.uk/python/mock/patch.html
http://mock.readthedocs.org/en/latest/index.html
"""
import unittest
import pprint
from mock import MagicMock, patch, call
def analyze(legend, thing):
print '====', legend, '===='
print thing, type(thing)
pprint.pprint(dict([(attr, getattr(thing, attr)) for attr in dir(thing)]))
class Foo:
def bar(self, x):
return x + 5
class MyTestCase(unittest.TestCase):
@patch('__main__.Foo')
def test_1(arg1, mock_class):
"""
In this usage, @patch() substitutes a MagicMock for a function's argument.
What we are doing here, for as long as we are inside the function, is we
are monkey-patching __main__ so that its 'Foo' attribute is no longer the
class defined above, it is the mock.
To expand on that, "@patch('__main__.Foo')" is saying, get the module whose
name is '__main__', create a mock, temporarily setattr the module's "Foo" to
refer to the mock instead of the class, and undo that when the function
finishes.
"""
assert isinstance(mock_class, MagicMock)
assert mock_class is Foo
@patch('__main__.Foo')
@patch('__main__.Foo.bar')
def test_2(self, mock_bar, mock_foo):
"""
@patch() is applied to arguments in reverse order
"""
assert isinstance(mock_bar, MagicMock)
assert isinstance(mock_foo, MagicMock)
assert mock_foo is Foo
mock_bar(None, 4)
assert mock_bar.mock_calls == [call(None, 4)]
def test_3(self):
"""
You don't need to use patch() as a function decorator. You can use it in a
"with" statement and then it applies for the duration of the code block. If
you write "as", then you're creating a second name for the mock, which can
be useful to remind yourself that it's really a mock.
"""
with patch('__main__.Foo') as MockFoo:
assert MockFoo is Foo
"""
MockFoo.return_value is the thing returned by the Foo() constructor, and
of course it is another MagicMock. When we assign the return value of its
bar "method", we can then perform a method call with predictable results.
"""
instance = MockFoo.return_value
instance.bar.return_value = 'baz'
assert Foo() is instance
assert Foo().bar() == 'baz'
def test_4(self):
"""
We can use the start and stop methods of the patcher returned by patch() to
explicitly control when the patching turns on and off. Also, we can use
"spec=True" to create a mock class that has mock methods corresponding to the
real methods in Foo.
"""
Original = Foo
patcher = patch('__main__.Foo', spec=True)
assert not isinstance(Foo, MagicMock)
patcher.start()
assert isinstance(Foo, MagicMock)
assert Foo is not Original
instance = Foo()
assert hasattr(instance, 'bar')
instance.bar(6)
with self.assertRaises(Exception):
# Fails because instance.bar is a mock and doesn't know it should add 5
assert instance.bar(6) == 11
assert isinstance(instance, Original)
patcher.stop()
assert Foo is Original
assert instance.method_calls == [call.bar(6), call.bar(6)]
def test_5(self):
foo = Foo()
self.assertEqual(11, foo.bar(6))
def new_bar(self, x):
return x + 10
@patch('__main__.Foo.bar', new=new_bar)
def test_6(self):
"""
We can patch a replacement method into a class, and the replacement lasts
for only the duration of this test method. In this case there are no
MagicMock instances anywhere.
"""
foo = Foo()
self.assertEqual(16, foo.bar(6))
test_7 = test_5
"""
Let's make certain that we are back to the original version of Foo, where the
bar method adds 5, not 10.
"""
suite = unittest.TestLoader().loadTestsFromTestCase(MyTestCase)
unittest.TextTestRunner(verbosity=2).run(suite)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment