Python mock.patch decorators affecting other unit tests in the same test suite
Asked Answered
Z

1

15

I wrote a unit test for main_function and asserted that it calls the function get_things inside it with an instance of a class, mocked with patch as a parameter:

@patch("get_something")
@patch("MyClass.__new__")
def test(self, mock_my_class_instance, mock_get_something):
    # Given
    dummy_my_class_instance = MagicMock()
    mock_my_class_instance.return_value = dummy_my_class_instance

    dummy_my_class_instance.get_things.return_value = {}

    # When
    main_function(parameter)

    # Then
    dummy_my_class_instance.get_things.assert_called_once_with(parameter["key1"], parameter["key2"])
    mock_get_something.assert_called_once_with(dummy_my_class_instance)

This is the main function:

def main_function(parameter):
    properties = get_properties(parameter)

    my_class_instance = MyClass()

    list_of_things = my_class_instance.get_things(properties["key-1"], properties["key-2"])
    an_object = get_something(my_class_instance)

    return other_function(list_of_things, an_object)

It passes individually but when run along with other tests that patch MyClass.get_things() it fails. This is the message:

Unhandled exception occurred::'NoneType' object has no attribute 'client'

It seems like the patch decorators are affecting each other.

I have tried to create the mocks inside the test function as variables rather than decorators but the problem persists. I have also tried to create a tearDown() to stop the patches but it doesn't seem to work.

Is there any way to isolate the patches or successfully discard them when mocking class instances?

Zincograph answered 12/4, 2018 at 13:57 Comment(4)
Hey, did you find a solution?Actionable
patch.stopall() ?Fernandes
Can you provide a full example code that can be executed with python3 -m unittest to demonstrate your problem?Chemotropism
I've tried just about everything as well. Has anyone come up with a solution?Aberdare
D
1

In this case, it might suit you better to slightly change the main_function so that it's a little more testable. You can do this in at least two ways:

You could add an optional parameter for the my_class_instance like this:

def main_function(parameter, my_instance = None):
    properties = get_properties(parameter)

    my_class_instance = my_instance if my_instance is not None else MyClass()

    list_of_things = my_class_instance.get_things(properties["key-1"], properties["key-2"])
    an_object = get_something(my_class_instance)

    return other_function(list_of_things, an_object)

Alternatively, if you don't want to change the API for the actual main_function, you could make a more testable helper function and use the main_function as a pass-through:

def _testable_main_function(parameter, my_instance = None):
    properties = get_properties(parameter)

    my_class_instance = my_instance if my_instance is not None else MyClass()

    list_of_things = my_class_instance.get_things(properties["key-1"], properties["key-2"])
    an_object = get_something(my_class_instance)

    return other_function(list_of_things, an_object)

def main_function(parameters):
    return _testable_main_function(parameters)

Admittedly, these all require code changes to your main function, so they might not be possible or fit your use case specifically.

With the class instance being a parameter instead of something that is patched out, you'll be able to inject the mocked object directly without having to patch class constructors. Without having a full example to play around in, it's hard to say that that's specifically what is going wrong, but I'd have a hunch that patching the constructor is what is interfering with the other tests

Decigram answered 14/3, 2023 at 15:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.