I'm having trouble replacing a simple method that calls a function in another module. From what I understand of mocking, you have to reference the method being called (in it's context, and not the original). Below is a simplified version of what I'm running and hoping that it's something simple that I need to learn about mocks. Is patch intended to be used only for Class and Class methods or am I doing something else wrong here?
Thanks, Steve
myapp.models.py
from myapp.backends import get_backend
class BasicClass(models.Model):
@staticmethod
def basic_method()
be = get_backend()
print be
myapp.backends._init_.py
def get_backend():
return 'original value'
test.py
# Referencing the import in myapp.models.basic_class
# vs directly importing myapp.backends
# as indicated here:
# http://www.voidspace.org.uk/python/mock/patch.html#where-to-patch
from myapp.models import get_backend
from myapp.models.basic_class import BasicClass
class ParsersTest(TestCase):
@patch('myapp.models.get_backend')
def test_simplified(self, moves_backend):
# Assertion fails
assert get_backend is moves_backend
# Assuming that assertion fails is why the original return value is always returned
moves_backend.return_value = 'new return value'
BasicClass.basic_method()
The purpose of patching with mock is to replace the reference to a module as it would be stored in sys.modules
and replace it with a reference to your mock. These means that code in the patched module will receive a reference to the mock object.
In your test, you are working with get_backend
. Which was imported at the top of your test module directly from myapp.models
before the decorator was applied. It is not patched. Your patch is in place, but only for code in myapp.models
which reference the get_backend
symbol imported there.
I know that's confusing. For me it was the hardest part of getting started with mock. If your test looked like this:
class ParsersTest(TestCase):
@patch('myapp.models.get_backend')
def test_simplified(self, moves_backend):
from myapp.models.basic_class import BasicClass
# Assertion should pass
BasicClass.basic_method()
moves_backend.assert_called_with()
moves_backend.return_value = 'new return value'
# As should this one (if you change the method to return instead of print)
self.assertEqual(BasicClass.basic_method(), 'new return value')
I think your test would be passing. The key difference here is that you are not testing against get_backend directly. You are testing a method that uses an imported get_backend after the patch has been applied.
update
The only other thing I can think of, without getting my hands on your actual code, is just that I don't like using patch as a decorator because you get less control over when the patch is applied/removed and worrying about getting a reference to the mock via args.
Try the context manager style:
with mock.patch('my app.models.get_backend') as moves_backend:
#...
with the rest of the test logic nested under that branch.
update part II
I just noticed in your original code that BasicClass
is in myapp.models.basic_class.py
.
If that's the case you patch should be applied to 'myapp.models.basic_class.get_backend'
because you are wanting to patch the reference to get_backend
that is being imported in the myapp.models.basic_class
submodule.