Skip to content

Instantly share code, notes, and snippets.

@hernantz
Created July 29, 2015 01:20
Show Gist options
  • Save hernantz/120cd2c95c753dd3304a to your computer and use it in GitHub Desktop.
Save hernantz/120cd2c95c753dd3304a to your computer and use it in GitHub Desktop.
The golden rule for where to mock.patch()
class SomeClass:
def some_method(self):
some_function()
def some_function():
pass
from a import SomeClass, some_function
def func1():
SomeClass().some_method()
def func2():
some_function()
class OtherClass:
def some_method(self):
some_other_function()
def some_other_function():
pass
import c
def func3():
c.OtherClass().some_method()
def func4():
c.some_other_function()
from b import func1, func2
from a import SomeClass
import c
from d import func3, func4
import mock
import unittest
class Test(unittest.TestCase):
@mock.patch.object(SomeClass, 'some_method')
def test_func1_calls_some_method(self, mocked):
func1()
mocked.assert_called_with()
@mock.patch.object(c.OtherClass, 'some_method')
def test_func3_calls_some_method(self, mocked):
func3()
mocked.assert_called_with()
@mock.patch.object(c, 'some_other_function')
def test_func3_calls_some_other_function(self, mocked):
func3()
mocked.assert_called_with()
@mock.patch.object(c, 'some_other_function')
def test_func4_calls_some_other_function(self, mocked):
func4()
mocked.assert_called_with()
@mock.patch('c.some_other_function')
def test_func3_calls_some_other_function_on_c(self, mocked):
func3()
mocked.assert_called_with()
@mock.patch('c.some_other_function')
def test_func4_calls_some_other_function_on_c(self, mocked):
func4()
mocked.assert_called_with()
@mock.patch('c.OtherClass.some_method')
def test_func3_calls_some_method_on_c(self, mocked):
func3()
mocked.assert_called_with()
@mock.patch('d.c.OtherClass.some_method')
def test_func3_calls_some_method_on_d(self, mocked):
func3()
mocked.assert_called_with()
@mock.patch('d.c.some_other_function')
def test_func3_calls_some_other_function_on_d(self, mocked):
func3()
mocked.assert_called_with()
@mock.patch('d.c.some_other_function')
def test_func4_calls_some_other_function_on_d(self, mocked):
func4()
mocked.assert_called_with()
@mock.patch('a.SomeClass.some_method')
def test_func1_calls_some_method_on_a(self, mocked):
func1()
mocked.assert_called_with()
@mock.patch('a.some_function')
def test_func1_calls_some_function_on_a(self, mocked):
func1()
mocked.assert_called_with()
@mock.patch('b.SomeClass.some_method')
def test_func1_calls_some_method_on_b(self, mocked):
func1()
mocked.assert_called_with()
@mock.patch('b.some_function')
def test_func1_calls_some_function_on_b(self, mocked):
func1()
mocked.assert_called_with()
@mock.patch('a.some_function')
def test_func2_calls_some_function_on_a(self, mocked):
func2()
mocked.assert_called_with()
@mock.patch('b.some_function')
def test_func2_calls_some_function_on_b(self, mocked):
func2()
mocked.assert_called_with()
if __name__ == '__main__':
unittest.main()

The golden rule for where to mock.patch()

Giving a talk about not using mocks, I mentioned that the golden rule to use patch is to always apply it on the place the object was used. There were other possiblilities suggested by one attendee so I decided to validate if that rule is still valid or not.

The files a.py, b.py, c.py, d.py try to generate all possible scenarios for importing a module and using it's functions, and test.py tries to combine all possible ways of patching.

The output is this:

> python test.py
.F...F..........
======================================================================
FAIL: test_func1_calls_some_function_on_b (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/hernantz/.virtualenvs/test/local/lib/python2.7/site-packages/mock.py", line 1201, in patched
    return func(*args, **keywargs)
  File "test.py", line 78, in test_func1_calls_some_function_on_b
    mocked.assert_called_with()
  File "/home/hernantz/.virtualenvs/test/local/lib/python2.7/site-packages/mock.py", line 831, in assert_called_with
    raise AssertionError('Expected call: %s\nNot called' % (expected,))
AssertionError: Expected call: some_function()
Not called

======================================================================
FAIL: test_func2_calls_some_function_on_a (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/hernantz/.virtualenvs/test/local/lib/python2.7/site-packages/mock.py", line 1201, in patched
    return func(*args, **keywargs)
  File "test.py", line 83, in test_func2_calls_some_function_on_a
    mocked.assert_called_with()
  File "/home/hernantz/.virtualenvs/test/local/lib/python2.7/site-packages/mock.py", line 831, in assert_called_with
    raise AssertionError('Expected call: %s\nNot called' % (expected,))
AssertionError: Expected call: some_function()
Not called

----------------------------------------------------------------------
Ran 16 tests in 0.022s

FAILED (failures=2)

test_func1_calls_some_function_on_b fails because the mock is being applied on b.some_function but the some_function is being used within a.SomeClass.some_method, thus the mock should've been applied on a.some_function.

test_func2_calls_some_function_on_a fails because the mock is being applied on a.some_function but the some_function is being used within b.func2, thus the mock should've been applied on b.some_function.

Conclusion: the golden rule is still valid.

PS: I'm not sure if I covered all the cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment