Testing a method call inside a function - python

I need to mock the object whose method I call in a function. The object is initialized inside the function and that's the problem. How to replace its implementation with mock?
Function code:
def handler (event, context):
"""Function, when call Yandex Server less"""
function_heandler = Handler(event=event, context=context)
response = function_heandler.run()
return response
Test code:
def test_main_call_handler():
with mock.patch('function.handler.Handler', new=mock.MagicMock()) as mock_handler:
handler({}, object())
mock_handler.run.assert_called()
And this, as expected, does not work. The function will be called in another module and I cannot pass the mock object there. Any ideas on how to fix this?

You should mock the class Handler instead.
Assuming Handler is imported from package.module, you can simply patch package.module.Handler:
def test_main_call_handler():
with mock.patch('package.module.Handler') as mock_handler:
handler({}, object())
mock_handler.run.assert_called()

One thing to remember when mocking, is that you mock (replace) the attribute of the tested module and not the called object.
To be concrete, let's say that your tested module is named using_my_handler_module.py and it looks like this (the first import line is important):
from my_handler_module import Handler
def handler(event, context):
"""Function, when call Yandex Server less"""
function_heandler = Handler(event=event, context=context)
response = function_heandler.run()
return response
Now, you need to mock the Handler attribute of using_my_handler_module, so when it's used, it's mocked.
So the test function would look like so:
import mock
from using_my_handler_module import handler
def test_main_call_handler():
with mock.patch('using_my_handler_module.Handler', new=mock.MagicMock()) as mock_handler:
handler({}, object())
mock_handler.return_value.run.assert_called()
Note we patch 'using_my_handler_module.Handler' .
Next, we check the assert called like this: mock_handler.return_value.run.assert_called()
The reason is that mock_handler.return_value is matching the return object of Handler(event=event, context=context) and you execute the run method on that object, so need to append .run.assert_called().
I myself find that mocking is confusing at times. This is why I wrote a helper library on top of pytest mock, called pytest-mock-generator. Here is how you can use it for your case:
def test_main_call_handler_using_pytest_mock_generator(mocker, mg):
mg.generate_uut_mocks(handler)
handler({}, object())
You use the mg (mock generator) fixture to generate the mocks for you - simply send the tested function as a parameter to mg.generate_uut_mocks. When you execute the test function this output would be printed and copied to your clipboard:
# mocked dependencies
mock_Handler = mocker.MagicMock(name='Handler')
mocker.patch('using_my_handler_module.Handler', new=mock_Handler)
Copy it to the beginning of your test and you now have:
def test_main_call_handler_using_pytest_mock_generator(mocker, mg):
# mocked dependencies
mock_Handler = mocker.MagicMock(name='Handler')
mocker.patch('using_mock_fixture.using_my_handler_module.Handler', new=mock_Handler)
handler({}, object())
Next, you want to generate the asserts section, so use another mg function called generate_asserts - send it your new mock:
def test_main_call_handler_using_pytest_mock_generator(mocker, mg):
# mocked dependencies
mock_Handler = mocker.MagicMock(name='Handler')
mocker.patch('using_mock_fixture.using_my_handler_module.Handler', new=mock_Handler)
handler({}, object())
mg.generate_asserts(mock_Handler)
When you execute your test now, you would get a bunch of generated asserts:
assert 1 == mock_Handler.call_count
mock_Handler.assert_called_once_with(context=_object_object_at_0x7f7fa60c2790_, event={})
mock_Handler.return_value.run.assert_called_once_with()
You don't need most of them, copy the last line into your test and the final version of your working test would be like this one:
def test_main_call_handler_using_pytest_mock_generator(mocker):
# mocked dependencies
mock_Handler = mocker.MagicMock(name='Handler')
mocker.patch('using_mock_fixture.using_my_handler_module.Handler', new=mock_Handler)
handler({}, object())
mock_Handler.return_value.run.assert_called_once_with()

Related

Python mock testing: How do I alter an Object in a mocked method?

I'm trying to test this kind of method that alters my incoming object, but it needs to be mocked because I can't run the code in a unit test case (requires massive files that aren't in the repo). I want to mock method_to_mock so that it sets the file_wrapper_object.status to whatever I want for that test case, notice the method doesn't return anything.
def method_to_mock(file_wrapper_object):
try:
process(file_wrapper_object) #<—- I can't run this in a test case
file_wrapper_object.status = "COMPLETE"
except:
file_wrapper_object.status = "FAILED"
def TestProcessFileWrapperObject(self):
file_wrapper_object = create_wrapper_object(arbitrary_data)
method_to_mock(file_wrapper_object)
self.assertEqual(file_wrapper_object.status, "COMPLETE")
How can I mock a method that doesn't return anything but alters the incoming object?
You can use the side_effect to specify a custom behavior for the mock object. Here is an example:
import unittest
from unittest.mock import MagicMock
def TestProcessFileWrapperObject(self):
file_wrapper_object = create_wrapper_object(arbitrary_data)
my_method_mock = MagicMock(return_value=None)
def side_effect(file_wrapper_object):
file_wrapper_object.status = "COMPLETE"
my_method_mock.side_effect = side_effect
my_method = my_method_mock
my_method(file_wrapper_object)
self.assertEqual(file_wrapper_object.status, "COMPLETE")
Please check this for more details.

How can I provide a non-fixture pytest parameter via a custom decorator?

We have unit tests running via Pytest, which use a custom decorator to start up a context-managed mock echo server before each test, and provide its address to the test as an extra parameter. This works on Python 2.
However, if we try to run them on Python 3, then Pytest complains that it can't find a fixture matching the name of the extra parameter, and the tests fail.
Our tests look similar to this:
#with_mock_url('?status=404&content=test&content-type=csv')
def test_file_not_found(self, url):
res_id = self._test_resource(url)['id']
result = update_resource(None, res_id)
assert not result, result
self.assert_archival_error('Server reported status error: 404 Not Found', res_id)
With a decorator function like this:
from functools import wraps
def with_mock_url(url=''):
"""
Start a MockEchoTestServer and call the decorated function with the server's address prepended to ``url``.
"""
def decorator(func):
#wraps(func)
def decorated(*args, **kwargs):
with MockEchoTestServer().serve() as serveraddr:
return func(*(args + ('%s/%s' % (serveraddr, url),)), **kwargs)
return decorated
return decorator
On Python 2 this works; the mock server starts, the test gets a URL similar to "http://localhost:1234/?status=404&content=test&content-type=csv", and then the mock is shut down afterward.
On Python 3, however, we get an error, "fixture 'url' not found".
Is there perhaps a way to tell Python, "This parameter is supplied from elsewhere and doesn't need a fixture"? Or is there, perhaps, an easy way to turn this into a fixture?
You can use url as args parameter
#with_mock_url('?status=404&content=test&content-type=csv')
def test_file_not_found(self, *url):
url[0] # the test url
Looks like Pytest is content to ignore it if I add a default value for the injected parameter, to make it non-mandatory:
#with_mock_url('?status=404&content=test&content-type=csv')
def test_file_not_found(self, url=None):
The decorator can then inject the value as intended.
consider separating the address from the service of the url. Using marks and changing fixture behavior based on the presence of said marks is clear enough. Mock should not really involve any communication, but if you must start some service, then make it separate from
with_mock_url = pytest.mark.mock_url('http://www.darknet.go')
#pytest.fixture
def url(request):
marker = request.get_closest_marker('mock_url')
if marker:
earl = marker.args[0] if args else marker.kwargs['fake']
if earl:
return earl
try:
#
earl = request.param
except AttributeError:
earl = None
return earl
#fixture
def server(request):
marker = request.get_closest_marker('mock_url')
if marker:
# start fake_server
#with_mock_url
def test_resolve(url, server):
server.request(url)

Python Kafka mocking return type of Kafka Consumer

I want to test a script when I use kafka-python package. I want to test type of return object for function
def _get_kafka_consumer() -> KafkaConsumer:
consumer = KafkaConsumer(bootstrap_servers=_KAFKA_BOOTSTRAP_SERVICE,
auto_offset_reset='earliest')
consumer.subscribe([_KAFKA_TOPIC_INPUT])
return consumer
My test class looks like
class TestVoiceToText(unittest.TestCase):
def test_get_kafka_consumer_output_type(self):
result = _get_kafka_consumer()
self.assertIsInstance(result, KafkaConsumer)
and of course it does not pass because there is no Kafka Cluster running so KafkaConsumer cannot be created. How can I mock that returning of KafkaConsumer(...) is of type KafkaConsumer without actual need of calling the actual constructor?
I managed to solve the problem by using patch function from unittest.mock package:
def test_get_kafka_consumer_output_type(self):
with patch('voice_to_text.kafka_connector.KafkaConsumer') as kafka_consumer_class_mock:
kafka_consumer_instance = _get_kafka_consumer()
kafka_consumer_class_mock_instance = kafka_consumer_class_mock.return_value
kafka_consumer_class_mock.assert_called_once()
self.assertEquals(kafka_consumer_class_mock_instance, kafka_consumer_instance)
It only checks if a result of _get_kafka_consumer() is actually an object returned by calling a function KafkaConsumer(). We don't care what actually that function is doing.
You would want the concept of spec in mocking. You can use autospec here I believe with success. Speccing will limit your mock to the original API of the object you're mocking, and allow it to pass isinstance tests as well. Here's the documentation on Autospeccing: https://docs.python.org/3.3/library/unittest.mock.html#autospeccing
Here's how I'd do your test:
#mock.patch('voice_to_text.kafka_connector.KafkaConsumer', autospec=True)
def test_get_kafka_consumer_output_type(self, kafka_consumer_mock):
kafka_consumer_instance = _get_kafka_consumer()
kafka_consumer_mock.assert_called_once_with(
bootstrap_servers=_KAFKA_BOOTSTRAP_SERVICE,
auto_offset_reset='earliest')
kafka_consumer_mock.return_value.subscribe.assert_called_once_with(
[_KAFKA_TOPIC_INPUT])
kafka_consumer_mock.assert_called_once()
self.assertEquals(kafka_consumer_mock.return_value, kafka_consumer_instance)
self.assertIsInstance(kafka_consumer_instance, KafkaConsumer)

How to mock a boto3 client object/call

I'm trying to mock one particular boto3 function. My module, Cleanup, imports boto3. Cleanup also has a class, "cleaner". During init, cleaner creates an ec2 client:
self.ec2_client = boto3.client('ec2')
I want to mock the ec2 client method: desribe_tags(), which python says is:
<bound method EC2.describe_tags of <botocore.client.EC2 object at 0x7fd98660add0>>
the furthest I've gotten is importing botocore in my test file and trying:
mock.patch(Cleaner.botocore.client.EC2.describe_tags)
which fails with:
AttributeError: 'module' object has no attribute 'EC2'
How do I mock this method?
Cleanup looks like:
import boto3
class cleaner(object):
def __init__(self):
self.ec2_client = boto3.client('ec2')
The ec2_client object is the one that has the desribe_tags() method. It's a botocore.client.EC2 object, but I never directly import botocore.
I found a solution to this when trying to mock a different method for the S3 client
import botocore
from mock import patch
import boto3
orig = botocore.client.BaseClient._make_api_call
def mock_make_api_call(self, operation_name, kwarg):
if operation_name == 'DescribeTags':
# Your Operation here!
print(kwarg)
return orig(self, operation_name, kwarg)
with patch('botocore.client.BaseClient._make_api_call', new=mock_make_api_call):
client = boto3.client('ec2')
# Calling describe tags will perform your mocked operation e.g. print args
e = client.describe_tags()
Hope it helps :)
You should be mocking with respect to where you are testing. So, if you are testing your cleaner class (Which I suggest you use PEP8 standards here, and make it Cleaner), then you want to mock with respect to where you are testing. So, your patching should actually be something along the lines of:
class SomeTest(Unittest.TestCase):
#mock.patch('path.to.Cleaner.boto3.client', return_value=Mock())
def setUp(self, boto_client_mock):
self.cleaner_client = boto_client_mock.return_value
def your_test(self):
# call the method you are looking to test here
# simple test to check that the method you are looking to mock was called
self.cleaner_client.desribe_tags.assert_called_with()
I suggest reading through the mocking documentation which has many examples to do what you are trying to do

Mocking urllib2.urlopen().read() for different responses

I am trying to mock the urllib2.urlopen library in a way that I should get different responses for different urls I pass into the function.
The way I am doing it in my test file now is like this
#patch(othermodule.urllib2.urlopen)
def mytest(self, mock_of_urllib2_urllopen):
a = Mock()
a.read.side_effect = ["response1", "response2"]
mock_of_urllib2_urlopen.return_value = a
othermodule.function_to_be_tested() #this is the function which uses urllib2.urlopen.read
I expect the the othermodule.function_to_be_tested to get the value "response1" on first call and "response2" on second call which is what side_effect will do
but the othermodule.function_to_be_tested() receives
<MagicMock name='urlopen().read()' id='216621051472'>
and not the actual response. Please suggest where I am going wrong or an easier way to do this.
The argument to patch needs to be a description of the location of the object, not the object itself. So your problem looks like it may just be that you need to stringify your argument to patch.
Just for completeness, though, here's a fully working example. First, our module under test:
# mod_a.py
import urllib2
def myfunc():
opened_url = urllib2.urlopen()
return opened_url.read()
Now, set up our test:
# test.py
from mock import patch, Mock
import mod_a
#patch('mod_a.urllib2.urlopen')
def mytest(mock_urlopen):
a = Mock()
a.read.side_effect = ['resp1', 'resp2']
mock_urlopen.return_value = a
res = mod_a.myfunc()
print res
assert res == 'resp1'
res = mod_a.myfunc()
print res
assert res == 'resp2'
mytest()
Running the test from the shell:
$ python test.py
resp1
resp2
Edit: Whoops, initially included the original mistake. (Was testing to verify how it was broken.) Code should be fixed now.

Categories

Resources