Question

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()
Was it helpful?

Solution

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_classsubmodule.

OTHER TIPS

I think you misunderstand. Remember: in Python, module and class members are (more or less) just simple variables. They might happen to contain a method, but they're still just variables. When you import something from another module, it just creates a variable on the importing module and drops some object into it.

The decorator @patch('myapp.models.get_backend') just replaces the variable on the myapp.models with a mock object. This is not what you want. By the time the patch is applied, myapp.models.basic_class has already been imported, so it has already imported a reference to the real method from myapp.models. (In other words, its get_backend variable holds the real method already.) You want to replace the variable where it's actually being used in myapp.models.basic_class like so:

@patch('myapp.models.basic_class.get_backend')

This will fill the get_backend variable on the myapp.models.basic_class with a mock. So when you call BasicClass.basic_method(), basic_method will look inside the myapp.models.basic_class.get_backend variable and find a mock.

So try this:

from myapp.models import basic_class

class ParsersTest(TestCase):
    @patch('myapp.models.basic_class.get_backend')
    def test_simplified(self, moves_backend):
        assert basic_class.get_backend is moves_backend
        moves_backend.return_value = 'new return value'
        basic_class.BasicClass.basic_method()

All that said, be careful what you test. You don't want to test everything in every possible way. Consider what value this test is providing you before writing it.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top