1

The Production file (production_file.py) is:

class MyError(Exception):
    pass

class MyClass:
    def __init__(self):
        self.value = None

    def set_value(self, value):
        self.value = value

    def foo(self):
        raise RuntimeError("error!")


class Caller:
    def bar(self, smth):
        obj = MyClass()
        obj.set_value(smth)

        try:
            obj.foo()
        except MyError:
            pass

        obj.set_value("str2")
        obj.foo()

Test file (test.py):

import unittest

from unittest.mock import patch
from unittest.mock import call
from production_file import MyClass, Caller

class MyTest(unittest.TestCase):
    def test_caller(self):
        with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
            my_class_mock_obj = MyClassMock.return_value
            my_class_mock_obj.foo.side_effect = [MyError("msg"), "text"]

            caller = Caller()
            caller.bar("str1")

            calls = [call("str1"), call("str2")]

            my_class_mock_obj.set_value.assert_has_calls(calls)

if __name__ == '__main__':
    unittest.main()

This above works. But if I move the production classes (MyError, MyClass, Caller) into the test file, and update patch to:

with patch('test.MyClass', autospec=MyClass) as MyClassMock:

then the instance method "foo" is no longer mocked.

Does anybody have any idea why that is?

I have also experienced a similar problem with some more complex code, where the production code is in my_package/src/production_file.py while the test is in my_package/tests/test_file.py. Python yields no error for the path, the path is correct, but still the mock doesn't work.

1 Answer 1

2

If you are running test.py as __main__ then it is not test.MyClass it would be __main__.MyClass, or in both cases __name__+".MyClass".

I was able to determine that the class used and the class patched were different by adding a print statement:

class Caller:
    def bar(self, smth):
        print(MyClass) #lets see what we are actually making an instance of...
        obj = MyClass()
        ...

When the patch is applied to the class that this is using you would see something like this:

<MagicMock name='MyClass' spec='MyClass' id='4387629656'>

But when the class in moved into test.py you will see something like:

<class '__main__.MyClass'>

Which indicates:

  1. There was no patching applied to MyClass (at least the one that is used for the test.)
  2. The name of the class that needs to be patched is __main__.MyClass

However It is quite likely that your "more... complicated situation" is not working because of a setup like this:

from production_file import MyClass

class MyError(Exception):
    pass


class Caller:
    def bar(self, smth):
        print(MyClass)
        obj = MyClass()
        ...

class MyTest(unittest.TestCase):
    def test_caller(self):
        with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
            ...

In this case production_file.MyClass is being patched and MyClass is being imported from production_file so the correct class is being patched but still the output is:

<class 'production_file.MyClass'>

This is because the Class was directly imported to the local namespace, so when the patch is applied to the production_file the local namespace is still unaffected, we can check that the patch was actually applied with:

...
def bar(self, smth):
    print(MyClass)
    from production_file import MyClass as pf_MyClass
    print(pf_MyClass)
...


#output:
<class 'production_file.MyClass'>
<MagicMock name='MyClass' spec='MyClass' id='4387847136'>

If this is the case you just need to import the module, not the class directly. Then once the patch is applied you will be using it right from the file:

import production_file

...
class Caller:
    def bar(self, smth):
        print(production_file.MyClass)
        obj = production_file.MyClass()
        ...

class MyTest(unittest.TestCase):
    def test_caller(self):
        with patch('production_file.MyClass', autospec=MyClass) as MyClassMock:
            ...
Sign up to request clarification or add additional context in comments.

7 Comments

Interesting... but why does not python yield an error, if the path is wrong?
because nothing is going wrong, it is just patching the wrong place, if you have a file test.py and in it you ` import test` you end up with two versions of the same file loaded with different names. if you added if __name__ == "__main__": from test import MyClass ... etc it would work as expected since then it is patching the class you are using for the test.
I also have a problem with a more... complicated situation, where I have my_package/src/my_file.py and my_package/tests/test.py. In this case, one of my tests uses patch("src.my_file.MyClass"), but still the mock doesn't work. Is there a way for me to debug / log what the test is actually patching?
does my edit provide a more comprehensive explication / analysis? I've never actually used unittest so I was mostly fumbling in the dark until I felt something I recognized, in this case it was inconsistent namespaces which I have seen before with pickling classes from __main__.
Thanks! that was the fix for my problem. Interestingly enough, at print(MyClass), it yielded the full path (starting with the package name). But the problem was fixed - and the mock now got to be printed out there.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.