Last active
May 18, 2021 15:18
-
-
Save wware/5367159 to your computer and use it in GitHub Desktop.
I find mock confusing and mystical. Here is some clarification.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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