Subclassing MagicMock in python - 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.

Related

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

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()
)

Python: Mocking an instance of a class inside a method using #patch

I have a method foo in Python that creates a Service class. I want to mock the Service class but when I run the test, it still attempts to instantiate the class. Here is the simplified version of my setup:
class Service:
def __init__(self, service):
self.service_stuff = service
def run_service(self):
do_service_stuff()
def foo:
new_service = Service("bar")
new_service.run_service()
Then my unit test:
#patch('mymodule.service_file.Service')
def test_foo(self, mock_service):
foo()
I would like to run foo, but have it use my mocked object instead of creating an actual Service instance, but instead, when I run it, it tries to instantiate an actual instance of Service() and runs foo() as usual even though it seems to recognize the string signature I've put into patch. Why is this happening?
Figured it out: The patch reference to the class had to be of its imported name in the method itself rather than the original class, similar to https://stackoverflow.com/a/32461515/4234853
So the patch should look like:
#patch('mymodule.foo_file.Service')
instead of trying to patch the class directly.
In this case, it might be easier to make your function more test-friendly:
def foo(cls=Service):
new_service = cls("bar")
new_service.run_service()
Then your test doesn't need to patch anything.
def test_foo(self):
mock_service = Mock()
foo(mock_service)

How to mock a module but not all the methods in it?

I am newbie in mocking. I've looked at the mock module and understood how to mock a specific method or module using patch decorator.
In order to mock a single method in a module, one way of doing it is:
#mock.patch('module1.method1')
def test_val(self, mock_method1):
mock_method1.return_value = "whatever_i_want"
In order to mock multiple methods in the same module, I can do this:
#mock.patch('module1.method2')
#mock.patch('module1.method1')
def test_val(self, mock_method1, mock_method2):
mock_method1.return_value = "whatever_i_want"
mock_method1.return_value = "whatever"
What I want is to mock a few methods in a module and keep the others as they are. If I mock the entire module, then every method or attribute in that module is mocked.
So, instead of mocking multiple methods individually like I've shown above, is there any way I can mock an entire module (by only keeping certain methods un-mocked)?
You can subclass the class under test and mock out all the methods in the constructor. Then under test you only use the mock class and not the real one.
class MockSubClass(RealClass):
def __init__(self, *args, **kwargs):
self.method_to_mock1 = mock.create_autospec(RealClass, 'method_to_mock1', ...)
...
class TestRealClass(TestCase):
def setUp(self, *args, **kwargs):
self.object_to_test = MockSubClass(...)

How do I patch a class in the same file as a class under test, that is initialized before the test begins?

(Nota bene: This is heavily modified from the original question, to include details I erroneously elided.)
This is the (summarized) file (common.py) I'm testing. It contains a decorator (derived from the Decorum library) that calls a class method on another object(A): I want to patch out A, because that code makes an external call I'm not testing.
from decorum import Decorum
class A:
#classmethod
def c(cls):
pass
class ClassyDecorum(Decorum):
"""Hack to allow decorated instance methods of a class object to run with decorators.
Replace this once Decorum 1.0.4+ comes out.
"""
def __get__(self, instance, owner):
from functools import partial
return partial(self.call, instance)
class B(Decorum):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def init(self, *args, **kwargs):
A.c()
return super().init(*args, **kwargs)
I'd like to #patch class A in my unittest, to isolate and check B.d()'s functionality. This is my unittest (located in test/test_common.py):
class BDecoratedClass(MagicMock):
#B
def dummy_func(self):
return "Success"
class TestB(TestCase):
#patch('unittest_experiment.A', autospec=True)
def test_d(self, mock_a):
b = BDecoratedClass()
b.dummy_func()
mock_a.c.assert_called_once_with() # Fails
Debugging the above, I see that A is never actually mocked: the code proceeds into A's code, so it makes sense that mock_a is never called, and thus the assertion fails. However, I'd like to properly monkey patch A. This approach works if I'm monkey patching an import that exists in common.py, but apparently not if the class is defined there?
Note that I think this is likely an issue of where I'm patching, that is #patch('common.A', autospec=True) should more likely be something like #patch('where.python.actually.finds.A.when.B.calls.A', autospec=True). But I'm very unclear on how to determine if that is the case, and if so, what the correct path is. For instance, #patch('BDecorated.common.A', autospec=True) does not work.
Thanks to #user2357112, I arrived at this solution. Caveat: I don't know if this is standard or 'best' practice, but it seems to work.
First, move the BDecoratedClass to it's own file in test/dummy.py. Then change the test to this:
class TestB(TestCase):
#patch('common.A', autospec=True)
def test_d(self, mock_a):
from test.dummy import BDecoratedClass
b = BDecoratedClass()
b.dummy_func()
mock_a.c.assert_called_once_with() # Succeeds
This forces the patch to execute prior to the import of the dummy class being decorated. It's a little weird because the import is inside the function, but for a test that seems fine.
Bigger Caveat:
This only works for the first test that imports something from the module where, in this case BDecoratedClass imports from. At that juncture everything else in the class has been loaded and cannot be patched.
It looks like patch substitutes the import and that's why it's too late at that point unless you work around it like you explained in your the answer. I found that using patch.object for individual methods also work. So something like:
class TestB(TestCase):
#patch.object(A, 'c')
def test_d(self, mock_c):
b = BDecoratedClass()
b.dummy_func()
mock_c.assert_called_once_with() # Succeeds

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)

Categories

Resources