简体   繁体   中英

Weird patching behavior

I have one class (Cat) and one method (say_hello) to patch. When I patch the class only, everything works well. When I patch the method only, it works too. When I patch both at the same time, the class is not patched but the method is properly patched.

main.py

from hello import say_hello
from cat import Cat

cat = Cat('kitty')

def main():
    print(say_hello())

hello.py

def say_hello():
    return "No mocked"

test.py

import unittest
from unittest.mock import patch

class TestStringMethods(unittest.TestCase):
    # cat.Cat is not patched correctly if both patch statements are there
    @patch('cat.Cat')
    @patch('main.say_hello')
    def test_kitty(self, say_hello_mock, cat_mock):
        say_hello_mock.return_value = "Mocked"
        from main import main
        main()



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

If you run the previous example, a real Cat is created. If you comment the patch of main.say_hello, a mock Cat is created.

I don't know why the patch decorator is not working, but you can use this solution:

def test_kitty(self):
    with patch('cat.Cat') as cat_mock:
        with patch('main.say_hello') as hello_mock:
            from main import main
            main()

On my question, the first patch called is main.say_hello. I will trigger an import of the main module and the cat will be instantiated as a real Cat instance. Then Cat is patched but too late.

The trick is to invert the order of the patch:

@patch('main.say_hello')
@patch('cat.Cat')
def test_kitty(self, cat_mock, say_hello_mock):
    say_hello_mock.return_value = "Mocked"
    from main import main
    main()

As decorators are called in an outward order, the Cat is patched then say_hello is patched triggering an import of the main module (which instantiates a mocked Cat).

The @patch('cat.Cat') call will at first import the cat module. At this point the cat.cat object is created from the original Cat class and it will reference this original class as its type.

Now you are replacing the original reference to cat.Cat with a reference to the mock. This has no effect on the object anymore.

You patch an attribute or method of the class and the instance will be change indirectly, but replacing the reference keeps the original class untouched.

The say_hello() is defined in hello.py , so this:

@patch('main.say_hello')

should be changed to:

@patch('hello.say_hello')

then you could decide which to be mocked first in the decorators.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM