Mock a subset of a Python class's methods and properties - python

I'm using the mock Python module for performing my tests.
There are times when I'm mocking a class, however I just want to mock some of its methods and properties, and not all of them.
Suppose the following scenario:
# module.py
class SomeClass:
def some_method(self):
return 100
def another_method(self):
return 500
# test.py
class Tests(unittest.TestCase):
#patch('module.SomeClass')
def test_some_operation(self, some_class_mock):
some_class_instance = some_class_mock.return_value
# I'm mocking only the some_method method.
some_class_instance.some_method.return_value = 25
# This is ok, the specific method I mocked returns the value I wished.
self.assertEquals(
25,
SomeClass().some_method()
)
# However, another_method, which I didn't mock, returns a MagicMock instance
# instead of the original value 500
self.assertEquals(
500,
SomeClass().another_method()
)
On the code above, once I patch the SomeClass class, calls to methods whose return_values
I didn't exlicitely set will return MagicMock objects.
My question is: How can I mock only some of a class methods but keep others intact?
There are two ways I can think of, but none of them are really good.
One way is to set the mock's method to the original class method, like this:
some_class_instance.another_method = SomeClass.another_method
This is not really desirable because the class may have a lot of methods and properties to
"unmock".
Another way is to patch each method I want explicitly, such as:
#patch('module.SomeClass.some_method')
def test_some_operation(self, some_method_mock):
But this doesn't really work if I want to mock the class itself, for mocking calls to the
initializer for example. The code below would override all SomeClass's methods anyway.
#patch('module.SomeClass.some_method')
#patch('module.SomeClass')
def test_some_operation(self, some_class_mock, some_method_mock):
Here is a more specific example:
class Order:
def process_event(self, event, data):
if event == 'event_a':
return self.process_event_a(data)
elif event == 'event_b':
return self.process_event_b(data)
else:
return None
def process_event_a(self, data):
# do something with data
def process_event_b(self, data):
# do something different with data
In this case, I have a general method process_event which calls a specific processing event depending on the supplied event.
I would like to test only the method process_event. I just want to know if the proper specific event is called depending on the event I supply.
So, in my test case what I want to do is to mock just process_event_a and process_event_b, call the original process_event with specific parameters, and then assert either process_event_a or process_event_b were called with the proper parameters.

Instead of patching the whole class, you must patch the object. Namely, make an instance of your class, then, patch the methods of that instance.
Note that you can also use the decorator #patch.object instead of my approach.
class SomeClass:
def some_method(self):
return 100
def another_method(self):
return 500
In your test.py
from unittest import mock
class Tests(unittest.TestCase):
def test_some_operation(self):
some_class_instance = SomeClass()
# I'm mocking only the some_method method.
with mock.patch.object(some_class_instance, 'some_method', return_value=25) as cm:
# This is gonna be ok
self.assertEquals(
25,
SomeClass().some_method()
)
# The other methods work as they were supposed to.
self.assertEquals(
500,
SomeClass().another_method()
)

Related

Unit Test with Mock and Multiple Functions

I cannot instantiate an object because it is an abstract class, so I have to use mocking in order to test my code.
I have been told this is best done by creating a new mock class.
class MockMyClass(MyClass):
def my_first_function(...):
The idea is that I then instantiate a MockMyClass object, where I can test private function in that.
I have read the python guide and researched other stack questions. Here, the theory behind mock has been well explained. Unfortunately, I am still lost with how mocking can be used in a large unittest for multiple functions. For instance:
If I have a class, from which other classes in the main code inherit functions from. This can take the form:
class SharedFunctions(AnotherClass):
first_function():
#do some important calculations to generate stuff.#
self.stuff = first_function_attribute_stuff
return returned_first_stuff
second_functions(returned_stuff)
returned_second_stuff = self.stuff + returned_first_stuff
return returned_second_stuff
and where the class SharedFunctions also inherits from another class (noting the abstract method) of the form:
class AnotherClass():
#abc.abstractmethod
def one_important_universal_function(...):
pass
I have tried to construct a unittest for the SharedFunctions piece of code.
This is what I have tried so far:
class MockSharedFunctions(SharedFunctions):
def first_function(...):
self.stuff = some value
returned_first_stuff = given some other value
return returned_first_stuff
def second_function
returned_second_stuff = another value.
return returned_second_stuff
class TestSharedFunctions(unittest.TestCase):
def test_first_function(self):
# insert code #
self.assertTrue(True)
def test_second_function(self):
# insert code #
self.assetEqual(output, expected)
self.assertTrue(True)
if __name__ == "__main__":
unittest.main()
Where insert code has been a number of various attempts to use mocking. However, I have not come across a clear example of how mock functions can be used to replace other functions, or a confirmation that this will work.
Thank you for any help.
A common issue is too over complicate the use of mocking functions. You can almost treat them like another class method. In your case, the abstractmethod decorator is probably generating the confusion.
This is something close to what you might need.
class MockSharedFunctions(SharedFunctions):
def one_important_universal_function(**args):
return 0
class TestSharedFunctions(unittest.TestCase):
def test_first_function(self):
mock = MockSharedFunctions()
mock_output = firstfunction(**args)
mock_stuff = mock.stuff
self.assertTrue(True)
self.assetEqual(mock_output, expected)
self.assetEqual(mock_stuff, expected)
def test_second_function(self):
mock = MockSharedFunctions()
mock.stuff = some_value
mock_output = second_function(**args)
self.assetEqual(mock_output, expected)
self.assertTrue(True)
if __name__ == "__main__":
unittest.main()
Here, in the MockSharedFunctions you are already inheriting SharedFunctions. As one_important_universal_function is an abstract method, it needs to be defined.

How do I mock a method on an object created by an #patch decorator?

I'm trying to use #patch decorators and mock objects to test the behavior of my code consuming some libraries. Unfortunately, I can't see any way to do what I'm trying to do using only decorators, and it seems like I should be able to. I realize that generally I should be more concerned with the return value of a method than the particular sequence of calls it makes to a library, but it's very difficult to write a test to compare one image to another.
I thought that maybe I could create instances of my mock objects and then use #patch.object(...) but that didn't work, and obviously #patch('MockObject.method') will only mock class methods. I can get the code to work by using with patch.object(someMock.return_value, 'method') as mockName but this makes my tests messy.
from some.module import SomeClass
class MockObject:
def method(self):
pass
class TestClass:
#patch('SomeClass.open', return_value=MockObject())
#patch('??????.method') # I can't find any value to put here that works
def test_as_desired(self, mockMethod, mockOpen):
instance = SomeClass.open()
instance.method()
mockOpen.assert_called_once()
mockMethod.assert_called_once()
#patch('SomeClass.open', return_value=MockObject())
def test_messy(self, mockOpen):
with patch.object(mockOpen.return_value, 'method') as mockMethod:
instance = SomeClass.open()
instance.method()
mockOpen.assert_called_once()
mockMethod.assert_called_once()
I think you are overcomplicating things:
import unittest.mock
#unittest.mock.patch.object(SomeClass, 'open', return_value=mock.Mock())
def test_as_desired(self, mock_open):
instance = SomeClass.open() # SomeClass.open is a mock, so its return value is too
instance.method()
mock_ppen.assert_called_once()
instance.method.assert_called_once()

Python3 mock: assert_has_calls for production-code methods?

I've got this production class:
class MyClass:
def __init__(self):
self.value = None
def set_value(self, value):
self.value = value
def foo(self):
# work with self.value here
# raise RuntimeError("error!")
return "a"
Which is being used from another place, like this:
class Caller:
def bar(self, smth):
obj = MyClass()
obj.set_value(smth)
# ...
# try:
obj.foo()
# except MyError:
# pass
obj.set_value("str2")
# obj.foo()
and I got this:
class MyError(Exception):
pass
In my test I want to make sure that Caller.bar calls obj.set_value, first with smth="a", then with smth="b", but I want it to really set the value (i.e. call the real set_value method). Is there any way for me to tell the mock to use the actual method, so I can later on read what it was called with?
P.S. I know that I can just change "foo" to require the parameter "smth" so I could get rid of "set_value", but I want to know if there is another option than this.
Okay, so I have tried this in my test:
def test_caller(self):
with patch('fullpath.to.MyClass', autospec=MyClass) as mock:
mock.foo.side_effect = [MyError("msg"), "text"]
caller = Caller()
caller.bar("str1")
calls = [call("str1"), call("str2")]
mock.set_value.assert_has_calls(calls)
But I see that the mock was not successful since the real "foo" is called when I wanted it to first raise MyError, then return "text".
Also, the assertion fails:
AssertionError: Calls not found.
Expected: [call('str1'), call('str2')]
Actual: []
The problem here is that you have mocked out your Class, and are not properly using the instance of your class. This is why things are not behaving as expected.
So, lets take a look at what is going on.
Right here:
with patch('fullpath.to.MyClass', autospec=MyClass) as mock:
So, what you are doing right here is mocking out your class MyClass only. So, when you are doing this:
mock.set_value.assert_has_calls(calls)
And inspect what is going on when you execute your unittest, your mock calls will actually contain this:
[call().set_value('str1'), call().foo(), call().set_value('str2')]
Pay attention to call as it is written as call(). call is with reference to your mock here. So, with that in mind, you need to use the called (aka return_value within context of the mocking world) mock to properly reference your mock object that you are trying to test with. The quick way to fix this is simply use mock(). So you would just need to change to this:
mock().set_value.assert_has_calls(calls)
However, to be more explicit on what you are doing, you can state that you are actually using the result of calling mock. Furthermore, it would actually be good to note to use a more explicit name, other than mock. Try MyClassMock, which in turn you name your instance my_class_mock_obj:
my_class_mock_obj = MyClassMock.return_value
So in your unit test it is more explicit that you are using a mocked object of your class. Also, it is always best to set up all your mocking before you make your method call, and for your foo.side_effect ensure that you are also using the instance mock object. Based on your recent update with your exception handling, keep your try/except without comments. Putting this all together, you have:
def test_caller(self):
with patch('tests.test_dummy.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)

Mock a class and a class method in python unit tests

I'm using python's unittest.mock to do some testing in a Django app. I want to check that a class is called, and that a method on its instance is also called.
For example, given this simplified example code:
# In project/app.py
def do_something():
obj = MyClass(name='bob')
return obj.my_method(num=10)
And this test to check what's happening:
# In tests/test_stuff.py
#patch('project.app.MyClass')
def test_it(self, my_class):
do_something()
my_class.assert_called_once_with(name='bob')
my_class.my_method.assert_called_once_with(num=10)
The test successfully says that my_class is called, but says my_class.my_method isn't called. I know I'm missing something - mocking a method on the mocked class? - but I'm not sure what or how to make it work.
Your second mock assertion needs to test that you are calling my_method on the instance, not on the class itself.
Call the mock object like this,
my_class().my_method.assert_called_once_with(num=10)
^^
A small refactoring suggestion for your unittests to help with other instance methods you might come across in your tests. Instead of mocking your class in each method, you can set this all up in the setUp method. That way, with the class mocked out and creating a mock object from that class, you can now simply use that object as many times as you want, testing all the methods in your class.
To help illustrate this, I put together the following example. Comments in-line:
class MyTest(unittest.TestCase):
def setUp(self):
# patch the class
self.patcher = patch('your_module.MyClass')
self.my_class = self.patcher.start()
# create your mock object
self.mock_stuff_obj = Mock()
# When your real class is called, return value will be the mock_obj
self.my_class.return_value = self.mock_stuff_obj
def test_it(self):
do_something()
# assert your stuff here
self.my_class.assert_called_once_with(name='bob')
self.mock_stuff_obj.my_method.assert_called_once_with(num=10)
# stop the patcher in the tearDown
def tearDown(self):
self.patcher.stop()
To provide some insight on how this is put together, inside the setUp method we will provide functionality to apply the patch across multiple methods as explained in the docs here.
The patching is done in these two lines:
# patch the class
self.patcher = patch('your_module.MyClass')
self.my_class = self.patcher.start()
Finally, the mock object is created here:
# create your mock object
self.mock_stuff_obj = Mock()
self.my_class.return_value = self.mock_stuff_obj
Now, all your test methods can simply use self.my_class and self.mock_stuff_obj in all your calls.
This line
my_class.my_method.assert_called_once_with(num=10)
will work if my_method is a class method.
Is it the case?
Otherwise, if my_method is just an normal instance method, then you will need to refactor the function do_something to get hold of the instance variable obj
e.g.
def do_something():
obj = MyClass(name='bob')
return obj, obj.my_method(num=10)
# In tests/test_stuff.py
#patch('project.app.MyClass')
def test_it(self, my_class):
obj, _ = do_something()
my_class.assert_called_once_with(name='bob')
obj.my_method.assert_called_once_with(num=10)

Subclassing MagicMock in python

I have a class that I need to patch, which works similar to this
class Foo(object):
def __init__(self, query):
self._query = query
def do_stuff(self):
# do stuff with self._query
How would I set up a mocking class for Foo such that
foo = MockFoo(query)
foo.do_stuff()
returns a mock result, but still taking into account the data passed in for query. I thought of subclassing MagicMock like this
class MockFoo(MagicMock):
def __init__(self, query, *args, **kwargs):
super(MagicMock, self).__init__(*args, **kwargs)
self._query
def do_stuff(self):
mock_result = self._query * 10
return MagicMock(return_value=mock_result)
but I can't quite figure out how to apply patch to use MockFoo instead of MagicMock in the actual TestCase. I need to be able to write a test similar to this.
with patch('x.Foo') as mock_foo:
# run test code
self.assertTrue(mock_foo.called)
self.assertEqual(mock_foo.wait.return_value, 20)
I know I can just use plain MagicMock with autospec=True or something and override the return values for each of the methods, but it would be nice to have a mock class which I can just use to replace the production class in one go. The key bit being, having to access the member variable self._query in the mock methods, while having it initialized in the constructor (just like the production class).
About you base question the answer is use new_callable parameter in patch:
with patch('x.Foo', new_callable=MockFoo) as mock_foo:
# run test code
self.assertTrue(mock_foo.called)
self.assertEqual(mock_foo.wait.return_value, 20)
If you need to add some argument to MockFoo init call consider that every argument not used in patch will be passet to the mock constructor (MockFoo in your case).
If you need to a wrapper of your production class maybe you are looking in the wrong place: mock is not wrapper.
When you mock something you don't really want to know how your mock do the work but just how your code use it and how you code react to mocked objects answers.

Categories

Resources