How to mock path like `module.Class`? using Python's Mock module? - python

How do I mock a class with this path ablib.Pin? And how do I mock the attributes of an instance off Pin? I would say it should work like this:
mock = MagicMock()
mock.Pin = MagicMock()
mock.Pin.kernel_id = 'N21'
mock.Pin.set_value.return_value = True
mock.Pin.get_value.return_value = 3
modules = {
'ablib': mock,
'ablib.Pin': mock.Pin,
}
patcher = patch.dict('sys.modules', modules)
patcher.start()
When I create a Pin instance I and call get_value or set_value I get a MockInstance instead of True or 3.
>>> p = Pin()
>>> p.set_value(3)
<MagicMock name='mock.Pin().set_value(3)' id='47965968'>
>>> p.kernel_id
<MagicMock name='mock.Pin().kernel_id' id='49231056'>
When I call kernel_id directly on Pin I get the result I need.
>>> Pin.kernel_id
'N21'
How do I mock ablib.Pin in a way that I get the values I want from an instance ablib.Pin()

Here is the working version with minimal changes to your code:
from unittest.mock import patch, MagicMock
mock = MagicMock()
mock.Pin().kernel_id = 'N21'
mock.Pin().set_value.return_value = True
mock.Pin().get_value.return_value = 3
modules = {
'ablib': mock
}
patcher = patch.dict('sys.modules', modules)
patcher.start()
Test:
>>> import ablib
>>> ablib.Pin
<MagicMock name='mock.Pin' id='139917634188240'>
>>> ablib.Pin
<MagicMock name='mock.Pin' id='139917634188240'>
>>> ablib.Pin()
<MagicMock name='mock.Pin()' id='139917634233616'>
>>> ablib.Pin()
<MagicMock name='mock.Pin()' id='139917634233616'>
>>> p = ablib.Pin()
>>> p.set_value(3)
True
>>> p.kernel_id
'N21'
Notice that I've removed mock.Pin = MagicMock() and removed mock.Pin from the dictionary. New MagicMock instance is created by default when you access existing MagicMock attribute or call it. The same instance will be returned next time you do that action. So there is no need to explicitly create MagicMock for mock.Pin (for Pin class), mock.Pin() (for Pin instance) and to register them with patch.dict(). Just registering mock is enough.
Thanks to patch.dict() it works even when module ablib doesn't exist. I believe that it is not possible to achieve with patch() and patch.object(). Nevertheless for real unit-testing I use patch() to mock a class and patch.object() to mock attributes of an instance for some particular test or group of tests. I have not been in a situation when I needed to mock a whole module, but I'm quite new to unit-testing and Python.

Related

MagicMock Mock'ed Function is not 'called'

I an trying to mock an api function send_message from stmplib.SMTP in Python using MagicMock.
a simplified version of my function looks like
#email_sender.py
def sendMessage():
msg = EmailMessage()
#Skipping code for populating msg
with smtplib.SMTP("localhost") as server:
server.send_message(msg)
I want to mock server.send_message call for my unit test. I have searched SO for some pointers and tried to follow a similar question.
Here is my unit test code based on the above question:
#email_sender_test.py
import email_sender as es
def test_send_message_input_success() -> None:
with patch("smtplib.SMTP", spec=smtplib.SMTP) as mock_client:
mock_client.configure_mock(
**{
"send_message.return_value": None
}
)
es.sendMessage()
#assert below Passes
assert mock_client.called == True
#assert below Fails
assert mock_client.send_message.called == True
Any idea what I am doing wrong which is causing the assert mock_client.send_message.called == True to fail?
Thanks!
You are testing the wrong mock attribute. Your code doesn’t call smtplib.SMTP.send_message. It might do so indirectly with the real object but a Mock can’t actually be sure of that nor should you want it to be that magical.
Take a look at what your code does with the smtp.SMTP mock; this is important because you’ll need to follow the same path in your test setup:
the mock is called: smtplib.SMTP("localhost"). Calling a mock will return a new mock object to stand in as the instance.
the instance mock is used as a context manager: with ... as server:. Python calls the __enter__() method and the return value of that method is bound to the name given by as. The instance mock will produce a new mock object for this. In fact, even the .__enter__ attribute is a mock object standing in for the method object.
The context manager mock, finally, is used to send the message: server.send_message(...).
The mock library actually creates these extra mock objects just once, then reuses them whenever needed, and you can access them in your setup, via the Mock.return_value attributes. You can use these to follow the same trail:
with patch("smtplib.SMTP", spec=smtplib.SMTP) as mock_smtp:
mock_instance = mock_smtp.return_value # smtplib.SMTP("localhost")
mock_cm = mock_instance.__enter__.return_value # with ... as server
# set up the server.send_message call return value
mock_cm.send_message.return_value = None
# run the function-under-test
es.sendMessage()
# assert things were called
assert mock_cm.send_message.called
Note: there is no need to add == True; that’s just doubling up on testing if true is true to produce true. assert already takes care of the check for you.
You can try these things out in an interactive interpreter too:
>>> import smtplib
>>> from unittest.mock import patch
>>> patcher = patch("smtplib.SMTP", spec=smtplib.SMTP)
>>> mock_smtp = patcher.start() # same as what with patch() as ... does
>>> smtplib.SMTP
<MagicMock name='SMTP' spec='SMTP' id='4356608448'>
>>> smtplib.SMTP("localhost")
<NonCallableMagicMock name='SMTP()' spec='SMTP' id='4356726496'>
>>> with smtplib.SMTP("localhost") as server:
... pass
...
>>> server
<MagicMock name='SMTP().__enter__()' id='4356772768'>
>>> server.send_message("some argument") # not set to return None here
<MagicMock name='SMTP().__enter__().send_message()' id='4356805728'>
>>> mock_smtp.return_value.__enter__.return_value.send_message.called
True
Note how each new mock object has a name that reflects how we got there! For future tests, try printing mocked objects in your code-under-test to see how to reach the right objects in your test setup, or use a debugger to inspect the objects. It’ll help you figure out what is going on.

Get value a method was called with during unit test

I am using pytest_mock to mock a function call. I would like to inspect the call to doB() to see if it was called with the value 3. How would I write the assert for this?
def testtest(mocker):
# arrange
mocker.patch.object(ClassA, 'doB', return_value=None)
sut = ClassA()
# act
actual = sut.party(4) # will make a call to doB
expected = 3
# assert
Maybe something like this:
def testtest(mocker):
# arrange
mocked_doB = mocker.patch.object(ClassA, 'doB', return_value=None)
sut = ClassA()
# act
actual = sut.party(4) # will make a call to doB
expected = 3
# assert
mocked_doB.assert_called_once_with(expected)
If you assign a value to the mocked object, in this case variable mocked_doB, you will get a MagicMock object back. With this MagicMock object, you have access to all kind of methods from the unittest.mock Mock class, e.g.
assert_called_once_with()
call_count,
and many more...
See here: https://docs.python.org/3/library/unittest.mock.html#the-mock-class
This works because with the pytest-mock framework you can directly access the mock module from mocker.
This is stated here under "Usage", https://pypi.org/project/pytest-mock/.

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 =======================================================================

Mocking a python dict with a dict of mocks

I want to mock a dict RESOURCES imported from another module. The method under test gets an object stored in the dict RESOURCES and calls that object's method query(). I want to test that query() is being called with the proper arguments, so the mocked RESOURCES needs to store mock objects that I can assert on.
How do I achieve this?
When I run the following code, assert_called_with fails because query is never called.
mymodule.py
from othermodule import RESOURCES # just a dict
def get_resource(date):
resource = RESOURCES["Prod"]
return resource.query(date)
test_mymodule.py
from unittest.mock import MagicMock, Mock, patch
from mymodule import get_resource
import pytest
#patch("mymodule.RESOURCES")
def test_get_resources(mock_resources):
mock_prod_resource = Mock()
mock_prod_resource.query = Mock(return_value=[1, 2])
d = {"Prod": mock_prod_resource}
mock_resources.__getitem__.side_effect = d.__getitem__
result = get_resource("2020-11-01")
mock_prod_resource.query.assert_called_with("2020-11-01")
assert result == [1, 2]

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