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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With