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

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

Related

Mock a method of a specific class instance in pytest tests

I can't figure out how to mock the method of a specific instance of a class.
I have the following class instances in my code
myInstance = MyCustomClass()
myInstanceToMock = MyCustomClass()
In a test, I would like to mock a method of the second one but not the first.
I can manage to mock the method for both by doing something like that:
import pytest
def the_mocked_method():
pass
def test_something(mocker):
mocker.patch.object(MyCustomClass, "the_method", new=the_mocked_method)
The problem is that both myInstance and myCustomInstance are affected and that doesn't fit my usecase.
Any way to mock only a method of a specific existing instance ?

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

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)

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