Mockito: mock a subscriptable class - python

I would like to mock ConfigParser class with Mockito to perform a simple test:
from configparser import ConfigParser
from unittest import TestCase
from mockito import when, mock, verify
section = "SETTINGS"
# Here is an example function to test
def get_config():
ini_file = "settings.ini"
parser = ConfigParser()
parser.read(ini_file)
settings = parser[section]
db_name = settings['db_name']
db_type = settings['db_type']
return (db_name, db_type)
class MyTest(TestCase):
def test_0_check_get_config(self):
parser = mock(ConfigParser)
expected_db_name = 'test.sqlite'
expected_db_type = 'sqlite'
settings = {
'db_name': expected_db_name,
'db_type': expected_db_type
}
# The line above will fail: TypeError: 'Dummy' object is not subscriptable
# I need to make the mocked class to return a 'settings' dictionary when 'SETTINGS' key is requested.
when(parser[section]).thenReturn(settings)
db_name, db_type = get_config()
assert db_name == 'test.sqlite'
assert db_type == 'sqlite'
The code is very self-explanatory. I would like the mocked class to return a corresponding dictionary when a particular key is requested.
There are a plenty of similar questions regarding 'regular' python unittest module. However, I would like to find an answer for Mockito mocking.

You need to stub ("when") the special dunder functions. E.g.
In [1]: from mockito import *
In [2]: m = mock()
In [3]: when(m).__getitem__("hi").thenReturn("ho")
Out[3]: <mockito.invocation.AnswerSelector at 0x1da3ba1d300>
In [4]: m["hi"]
Out[4]: 'ho'
This solves the "subscriptable" problem but keep in mind that the exact example you gave uses a global ConfigParser (t.i. you don't dependency inject here). Just calling mock(ConfigParser) will not monkey-patch the global object. Instead it creates a new object ("instance") you can pass around ("inject").

Related

How to patch a module method that is called within a class?

I have the following structure:
# create.py
import sshHandler
class Create:
def __init__(self):
self.value = sshHandler.some_method()
# sshHandler.py
def some_method():
return True
If I kow try to patch sshHandler.some_method it will not work as expected
from unittest import TestCase
from unittest.mock import patch
import create
class TestCreate(TestCase):
#patch("sshHandler.some_method")
def test_create(self, mock_ssh):
mock_ssh.return_value = False
c = create.Create()
# c.value = True but should be false
The result I am looking for is that some_method would be patched in create as well (and return false). If I just call some_method in the context of test_create it works as expected. How do I fix the patch so that it is also active in the Create class when accessing sshHandler?
I saw this question Why python mock patch doesn't work?, but couldn't solve my problem with the information given there.
You've patched the wrong module. Instead patch the sshHandler.some_method patch create.sshHandler.some_method. You must patch the object of module you're handling.

Pytest - mocking a side_effect on mock's nested attribute function / method

I have a fixture mocking an external library like so, using pytest-mock, which is a wrapper around unittest.mock.
# client.py
import Test as TestLibrary
class LibraryName():
def get_client():
return TestLibrary.Library()
# library_service.py
def using_library():
'''
Edited note: Library().attribute behind the scenes is set to
self.attribute = Attribute()
so this may be affecting the mocking
'''
client = LibraryName.get_client()
return client.attribute.method()
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
# test_library_service.py
def test_library_method(library_client_mock):
result = using_library()
I can mock a return value like so:
def test_library_method(library_client_mock):
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
result = using_library()
assert result == "test"
but I can't mock throwing an Exception with side_effect
def test_library_method(library_client_mock):
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError # doesn't work
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError() # doesn't work
attrs = { 'attribute.method.side_effect': TypeError }
library_client_mock.configure_mock(**attrs) # doesn't work
with pytest.raises(TypeError):
using_library() # fails assertion
what I missing here?
These are the errors in your code:
Change:
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
To:
library_client_mock.return_value.attribute.method.return_value = "test"
Change:
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError
To:
library_client_mock.return_value.attribute.method.side_effect = TypeError
Explanation
The .return_value must only be used for callable objects e.g. a function as documented:
return_value
Set this to configure the value returned by calling the mock:
>>> mock = Mock()
>>> mock.return_value = 'fish'
>>> mock()
'fish'
Thus, you can use .return_value only for the following:
TestLibrary.Library()
TestLibrary.Library().attribute.method()
But not for:
TestLibrary.Library().attribute
Because .attribute is not a callable e.g. TestLibrary.Library().attribute().
Warning
The way you are patching Library is via its source location at Test.Library (or aliased as TestLibrary.Library). specifically via:
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
It works currently because the way you import and use it is via the root path.
# client.py
import Test as TestLibrary
...
return TestLibrary.Library()
...
But if we change the way we imported that library and imported a local version to client.py:
# client.py
from Test import Library # Instead of <import Test as TestLibrary>
...
return Library() # Instead of <TestLibrary.Library()>
...
It will now fail. Ideally, you should patch the specific name that is used by the system under test, which here is client.Library.
import client
return mocker.patch.object(client, 'Library')
Unless you are sure that all files that will use the library will import only the root and not a local version.
#Niel Godfrey Ponciano set me on the right path with this syntax for the side_effect
library_client_mock.return_value.attribute.method.side_effect = TypeError
but it wasn't enough.
In
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
I had to add an extra mock:
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
mock_library_client = mocker.patch.object(TestLibrary, 'Library')
# option 1
mock_attribute = Mock()
# option 2, path to Library.attribute = Attribute()
mock_attribute = mocker.patch.object(TestLibrary.services, 'Attribute', autospec=True)
mock_library_client.attach_mock(mock_attribute, "attribute")
return mock_library_client
and then both of the following statements worked as expected. Although I am not sure why return_value works out of the box without an attached mock, but side_effect does not.
# return_value set correctly
# NOTE return_value needed after each
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
# side_effect set correctly
# NOTE return_value not needed after "attribute"
library_client_mock.return_value.attribute.method.side_effect = TypeError

Python mocking function that isn't called directly

In my object, I have several "Loader" functions for different types of data that might require loading. They're stored as:
import foo
import bar
class MyClass
LOADERS = {
'foo': {
'context_manager': False,
'func': foo.data.loader,
'kwargs': {'data_orientation': 'columns'}
},
'bar': {
'context_manager': True,
'func': bar.load_data
'kwargs': {}
}
}
def load_data(self, load_type, path):
func = self.LOADERS[load_type]['func']
kwargs = self.LOADERS[load_type]['kwargs']
if self.LOADERS[load_type]['context_manager']:
with open(path, 'rb') as f:
self.data=func(f, **kwargs)
else:
self.data = func(path, **kwargs)
This is working very well in practice, but I'm finding it's murder to test.
When I write my test:
from mock import MagicMock, patch
import sys
sys.modules['foo'] = MagicMock()
sys.modules['bar'] = MagicMock()
from mypackage import MyClass
#patch('foo.data.loader')
def test_load_foo(mock_loader):
my_obj = MyClass()
my_obj.load_data('foo', 'path/to/data')
mock_loader.assert_called_once()
it fails. Called 0 times.
I halfway suspect that it's because it's not being called directly. But that shouldn't really matter.
Can anyone offer any suggestions? I'm using pytest as my testing engine, but I've found it plays nicely with mock in the past.
Thanks!
The issue arises because you are trying to patch a function within a class attribute as opposed to an instance attribute. The behavior is not the same. This does however offer us some flexibility as it is a class attribute, thus we can modify the object directly on the class we instantiate in our test.
from unittest.mock import MagicMock
from mypackage import MyClass
def test_load_foo():
key_to_test = "foo"
mock_loader = MagicMock()
my_obj = MyClass()
my_obj.LOADERS[key_to_test]["func"] = mock_loader
my_obj.load_data(key_to_test, 'path/to/data')
mock_loader.assert_called_once()
Instead of trying to patch the function, we modify our objects instance to use the MagicMock.
Using this approach I receive the following message:
collected 1 item
tests/test_mypackage.py . [100%]
======================================================================= 1 passed in 0.01s =======================================================================

unittest.mock: Set custom attribute (variable) on specced mock object

I am trying to mock an existing object from another library's class for unit tests with pytest.
However, the attributes (not methods) from the other library are mostly set during runtime.
What I want to achieve
Get all the benefits of mocking the object with spec
Set the (nested) attributes (not methods) needed for my unittests to simulate as if they were set during object creation
from unittest.mock import Mock
from otherlib import ClassName
def test_stuff():
mock_object = Mock(spec=ClassName)
mock_object.data.outward.key = 12345
assert mock_object.data.outward.key == 12345 # AttributeError: Mock object has no attribute 'data'
I made these attempts in code changes but with no success
...
def test_stuff():
mock_object = Mock(spec=ClassName, **{'data.outward.key': 12345})
assert mock_object.data.outward.key == 12345
...
def test_stuff():
mock_object = Mock(spec=ClassName)
attrs = {'data.outward.key': 12345}
mock_object.configure_mock(**attrs)
assert mock_object.data.outward.key == 12345
The best I came up with is using another Mock object to use when setting the main mock object's attributes. It works, but I guess there is a better solution for this...?
from unittest.mock import Mock
from otherlib import ClassName
def test_stuff():
mock_data = Mock(spec=["outward"], key=12345)
mock_object = Mock(spec=ClassName, data=mock_data)
mock_object.data.outward.key = 12345
assert mock_object.data.outward.key == 12345 # tests.py::test_stuff PASSED [100%]

Python mock library not returning correct mock object for 'patch'

I'm trying to get the mock library to return a specific mock object with patch. No matter what I try, if I instantiate a new object inside my 'with' clause, I get a generic Mock object, not my customised one.
Here's my code using logging as an example:
import logging
my_mock = Mock()
my_mock.id = 'i-12341234'
with patch('logging.PlaceHolder', new=my_mock) as mockobj:
print mockobj.id # correctly prints 'i-12341234'
newobj = logging.PlaceHolder(None)
print newobj.id # prints <Mock name='mock().id' id='47607184'>
How can I get mock to return my_mock every time a new instance of logging.Placeholder is instantiated?
As it turns out subclassing Mock and passing it using new_callable works. Since I've spent the time trying to get the version above working I'd still appreciate and answer.
import logging
from mock import Mock, patch
my_mock = Mock()
my_mock.id = 'i-12341234'
with patch('logging.PlaceHolder', new=my_mock) as mockobj:
mockobj.return_value = mockobj # <-- added this line.
print mockobj.id
newobj = logging.PlaceHolder(None)
print newobj.id
Alternative
import logging
from mock import Mock, patch
my_mock = Mock()
my_mock.id = 'i-12341234'
my_mock.return_value = my_mock # <---
with patch('logging.PlaceHolder', new=my_mock) as mockobj:
assert mockobj.id == 'i-12341234'
newobj = logging.PlaceHolder(None)
assert newobj.id == 'i-12341234'

Categories