Fudge: @patch not working when from X import Y'ing instead of import X?

353 views Asked by At

I came across a strange bahviour of the patch decorator in Fudge 1.0.3. It does not patch the module when importing classes via

from <module> import <class>

but works fine when importing

import <module>

with the corresponding code adaption.

Here's a minimalized setup:

mdle.py:

class Klaas(object):

    def __init__(self):
        # easyest way to signal this class has been instantiated accidently
        raise Exception(">:[")

some.py (test not working):

from mdle import Klaas()
def instantiate():
    instance = Klaas()

some.py (test working):

import mdle
def instantiate():
    instance = mdle.Klaas()

some_test.py:

import unittest
import fudge

import some

class SomeTest(unittest.TestCase):

    @fudge.patch("mdle.Klaas")
    def test_somethingb(self, Klaas_mock):
        klaas_inst = (Klaas_mock.expects_call()
                            .returns_fake())


        # Receiving the exception
        some.instantiate()

Should I patch in a different way? Is this a limitation of Fudge, or a bug?

1

There are 1 answers

1
quantoid On

You have to patch the name where the object is being referenced, not where it's being defined.

Remember modules are just objects with a dict of names pointing to other objects (a class is an object too). The same object can have multiple (possibly identical) names in different modules. Patching makes a name temporarily point to a Fake rather than to the original object.

I assume in your first (not working) some.py module you meant:

from mdle import Klass

That creates a name some.Klass that gets used in that module. The name happens to match the name in mdle by default, but you actually have two names pointing to the same class object. It's the name in some you need to patch if you want to use a fake instead, because that's the name used to reference the class in the module under test.

Your test patches mdle.Klass which is not the name used in some, so your code still picks up the real class object using its own unpatched name. You need to patch some.Klass instead in this case.

In your second (working) some.py you import the whole mdle module and reference the class using the name in that module. That's why patching mdle.Klass works in that case, you're patching the name that's being used.