How to mock a boto3 client object/call - python

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

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.

Mock a Glue Job unit test case

I am in need to mock a glue job to check if it exists for a unit test case.
The following code is used for it.
class GlueJob:
"""Function mocking glue job."""
def custom_get_glue_job(self,glue_client):
"""Test Glue Job"""
response = glue_client.get_job(JobName="abc")
return response
import unittest
import sys
from unittest import mock
from mock import patch
class TestGlueJobs(unittest.TestCase):
"""Function mocking glue job"""
#patch.object(GlueJob, 'custom_get_glue_job',return_value=True)
def check_glue_job_exists(self, glue_job):
"""Function printing python version."""
session = boto3.session.Session()
glue_client = session.client('glue', region_name='us-west-1')
GlueJob.custom_get_glue_job(glue_client)
print("success")
glue_job = GlueJob
TestGlueJobs().check_glue_job_exists()
I am getting the following errors:
No value for argument 'glue_client' in unbound method call
No value for argument 'glue_job' in method call
I have passed on the values for both this argument. Not sure what I am missing here.
Thanks.

Testing a method call inside a function

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

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)

Mocking ftplib.FTP for unit testing Python code

I don't know why I'm just not getting this, but I want to use mock in Python to test that my functions are calling functions in ftplib.FTP correctly. I've simplified everything down and still am not wrapping my head around how it works. Here is a simple example:
import unittest
import ftplib
from unittest.mock import patch
def download_file(hostname, file_path, file_name):
ftp = ftplib.FTP(hostname)
ftp.login()
ftp.cwd(file_path)
class TestDownloader(unittest.TestCase):
#patch('ftplib.FTP')
def test_download_file(self, mock_ftp):
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftp.cwd.assert_called_with('pub/files')
When I run this, I get:
AssertionError: Expected call: cwd('pub/files')
Not called
I know it must be using the mock object since that is a fake server name, and when run without patching, it throws a "socket.gaierror" exception.
How do I get the actual object the fuction is running? The long term goal is not having the "download_file" function in the same file, but calling it from a separate module file.
When you do patch(ftplib.FTP) you are patching FTP constructor. dowload_file() use it to build ftp object so your ftp object on which you call login() and cmd() will be mock_ftp.return_value instead of mock_ftp.
Your test code should be follow:
class TestDownloader(unittest.TestCase):
#patch('ftplib.FTP', autospec=True)
def test_download_file(self, mock_ftp_constructor):
mock_ftp = mock_ftp_constructor.return_value
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftp_constructor.assert_called_with('ftp.server.local')
self.assertTrue(mock_ftp.login.called)
mock_ftp.cwd.assert_called_with('pub/files')
I added all checks and autospec=True just because is a good practice
Like Ibrohim's answer, I prefer pytest with mocker.
I have went a bit further and have actually wrote a library which helps me to mock easily. Here is how to use it for your case.
You start by having your code and a basic pytest function, with the addition of my helper library to generate mocks to modules and the matching asserts generation:
import ftplib
from mock_autogen.pytest_mocker import PytestMocker
def download_file(hostname, file_path, file_name):
ftp = ftplib.FTP(hostname)
ftp.login()
ftp.cwd(file_path)
def test_download_file(mocker):
import sys
print(PytestMocker(mocked=sys.modules[__name__],
name=__name__).mock_modules().prepare_asserts_calls().generate())
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
When you run the test for the first time, it would fail due to unknown DNS, but the print statement which wraps my library would give us this valuable input:
...
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch('test_29817963.ftplib', new=mock_ftplib)
...
import mock_autogen
...
print(mock_autogen.generator.generate_asserts(mock_ftplib, name='mock_ftplib'))
I'm placing this in the test and would run it again:
def test_download_file(mocker):
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch('test_29817963.ftplib', new=mock_ftplib)
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
import mock_autogen
print(mock_autogen.generator.generate_asserts(mock_ftplib, name='mock_ftplib'))
This time the test succeeds and I only need to collect the result of the second print to get the proper asserts:
def test_download_file(mocker):
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch(__name__ + '.ftplib', new=mock_ftplib)
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftplib.FTP.assert_called_once_with('ftp.server.local')
mock_ftplib.FTP.return_value.login.assert_called_once_with()
mock_ftplib.FTP.return_value.cwd.assert_called_once_with('pub/files')
If you would like to keep using unittest while using my library, I'm accepting pull requests.
I suggest using pytest and pytest-mock.
from pytest_mock import mocker
def test_download_file(mocker):
ftp_constructor_mock = mocker.patch('ftplib.FTP')
ftp_mock = ftp_constructor_mock.return_value
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
ftp_constructor_mock.assert_called_with('ftp.server.local')
assert ftp_mock.login.called
ftp_mock.cwd.assert_called_with('pub/files')

Categories

Resources