Mocking and assert_called_once_with() - python

I want to assert_called_once_with for the mocked object, here is what I do:
log_service.py
def insert_log(self, log):
session = boto3.Session(profile_name='test')
dynamodb = session.resource('dynamodb')
table = dynanodb.Table('log_info')
table.put_item(Item=log.__dict__)
test_log_service.py
class Test(Unittest.TestCase):
def setUp(self):
self.log_service = LogService()
#patch("boto3.Session")
def test_insert_log(self, mock_session):
log = LogModel()
mock_dynamodb = MagicMock()
mock_table = MagicMock()
mock_session.resource = mock_dynamodb
mock_dynamodb.Table = mock_table
self.log_service.insert_log(log)
mock_table.put_item.assert_called_once_with(Item=log)
I expected that it should true, however, it always return:
AssertionError: Expected 'put_item' to be called once. Called 0 times.
Could someone help me to point out the problem?

Related

Mock object with pyTest

I have a function I need to test:
def get_user_by_username(db, username: str) -> Waiter:
"""Get user object based on given username."""
user = db.query(Waiter).filter(Waiter.username == username).first()
return user
Here I try to mock DB call and return correct Waiter object, so my test is:
def test_get_user_by_username(mocker):
waiter = Waiter(id=10, username="string", password="1111")
db_mock = mocker.Mock(return_value=waiter)
result = get_user_by_username(db=db_mock, username="string")
assert result.json()["username"] == "string"
But I get an error TypeError: 'Mock' object is not subscriptable. How I can do it?
According to #Don Kirkby answer, the right solution is:
def test_get_user_by_username(mocker):
waiter = Waiter(id=10, username="string", password="1111")
filter_method = mocker.Mock()
filter_method.first = mocker.Mock(return_value=waiter)
query = mocker.Mock()
query.filter = mocker.Mock(return_value=filter_method)
db_mock = mocker.Mock()
db_mock.query = mocker.Mock(return_value=query)
db_mock.query.filter.first = mocker.Mock(return_value=waiter)
result = get_user_by_username(db=db_mock, username="string")
assert vars(result)["username"] == "string"
You're calling several methods, so you need to mock all of them:
query()
filter()
first()
Here's my best guess at how to do that with your code:
def test_get_user_by_username():
waiter = Waiter(id=10, username="string", password="1111")
filter_method = mocker.Mock()
filter_method.first = mocker.Mock(return_value=waiter)
query = mocker.Mock()
query.filter = mocker.Mock(return_value=filter_method)
db_mock = mocker.Mock()
db_mock.query = mocker.Mock(return_value=query)
db_mock.query.filter.first = mocker.Mock(return_value=waiter)
result = get_user_by_username(db=db_mock, username="string")
assert result.json()["username"] == "string"
I'm not sure what database library you're using, but I recommend looking for a library that provides mock versions of the database methods. For example, I contributed a few features to django-mock-queries that lets you mock out the Django ORM. Either that, or separate the logic you want to test from the database access code.

How can I mock an attribute of a class using the mock decorator?

#these classes live inside exchanges/impl/tse/mixins.py
class PacketContext:
capture_tstamp = None
def __init__(self, capture_tstamp=None):
self.capture_tstamp = capture_tstamp
class SubParserMixin():
def __init__(self):
self.context = PacketContext()
def on_packet(self, packet):
self.context.capture_tstamp = packet.capture_timestamp
self.parse_er_data(packet.payload)
#this mock test lives in another python file
from exchanges.impl.tse.mixins import PacketContext
#patch.object(PacketContext, 'capture_tstamp', 1655417400314635000)
def test_receive_timestamp(self):
"""
test receive_timestamp is passed down correctly from PacketContext to on_packet()
"""
assert self.context.capture_tstamp == 1655417400314635000
I am trying to mock the self.capture_tstamp attribute in the PacketContext() class.
But in the above, I am getting an error that says
AssertionError: assert None == 1655417400314635000
E + where None = <exchanges.impl.tse.mixins.PacketContext object at 0x7fb324ac04c0>.capture_tstamp
E + where <exchanges.impl.tse.mixins.PacketContext object at 0x7fb324ac04c0> = <tests.unit.exchanges.tse.test_quote_write.TestTSE testMethod=test_receive_timestamp>.context
It seems very strange that the program is not recognising PacketContext().
You can make use of the patch.object decorator as below
class PacketContext:
capture_tstamp = None
def __init__(self, capture_tstamp=None):
self.capture_tstamp = capture_tstamp
<import_PacketContext_here>
#patch.object(PacketContext, 'capture_tstamp', 1655417400314635000)
def test_receive_timestamp():
test_instance = PacketContext()
assert test_instance.capture_tstamp == 1655417400314635000

Not sure why MyMock.env["key1"].search.side_effect=["a", "b"] works but MyMock.env["key1"] = ["a"] with MyMock.env["key2"] = ["b"] does not work

I had created a simple example to illustrate my issue. First is the setup say mydummy.py:
class TstObj:
def __init__(self, name):
self.name = name
def search(self):
return self.name
MyData = {}
MyData["object1"] = TstObj("object1")
MyData["object2"] = TstObj("object2")
MyData["object3"] = TstObj("object3")
def getObject1Data():
return MyData["object1"].search()
def getObject2Data():
return MyData["object2"].search()
def getObject3Data():
return MyData["object3"].search()
def getExample():
res = f"{getObject1Data()}{getObject2Data()}{getObject3Data()}"
return res
Here is the test that failed.
def test_get_dummy1():
dummy.MyData = MagicMock()
mydummy.MyData["object1"].search.side_effect = ["obj1"]
mydummy.MyData["object2"].search.side_effect = ["obj2"]
mydummy.MyData["object3"].search.side_effect = ["obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
The above failed with run time error:
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/mock.py:1078: StopIteration
Here is the test that passed:
def test_get_dummy2():
dummy.MyData = MagicMock()
mydummy.MyData["object1"].search.side_effect = ["obj1", "obj2", "obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
Am I missing something? I would have expected test_get_dummy1() to work and test_get_dummy2() to fail and not vice versa. Where and how can I find/learn more information about mocking to explain what is going on...
MyData["object1"] is converted to this function call: MyData.__getitem__("object1"). When you call your getExample method, the __getitem__ method is called 3 times with 3 parameters ("object1", "object2", "object3").
To mock the behavior you could have written your test like so:
def test_get_dummy_alternative():
mydummy.MyData = MagicMock()
mydummy.MyData.__getitem__.return_value.search.side_effect = ["obj1", "obj2", "obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
Note the small change from your version: mydummy.MyData["object1"]... became: mydummy.MyData.__getitem__.return_value.... This is the regular MagicMock syntax - we want to to change the return value of the __getitem__ method.
BONUS:
I often struggle with mock syntax and understanding what's happening under the hood. This is why I wrote a helper library: the pytest-mock-generator. It can show you the actual calls made to the mock object.
To use it in your case you could have added this "exploration test":
def test_get_dummy_explore(mg):
mydummy.MyData = MagicMock()
mydummy.getExample()
mg.generate_asserts(mydummy.MyData, name='mydummy.MyData')
When you execute this test, the following output is printed to the console, which contains all the asserts to the actual calls to the mock:
from mock import call
mydummy.MyData.__getitem__.assert_has_calls(calls=[call('object1'),call('object2'),call('object3'),])
mydummy.MyData.__getitem__.return_value.search.assert_has_calls(calls=[call(),call(),call(),])
mydummy.MyData.__getitem__.return_value.search.return_value.__str__.assert_has_calls(calls=[call(),call(),call(),])
You can easily derive from here what has to be mocked.

Parametrized test fixtures in pytest

I have a test suite where I create instance of object and replace kafka consumer in that object with my mock consumer, such as:
def mock_consumer(fixtures):
with open(fixtures) as fixturefile:
messages = fixturefile.readlines()
for msg in messages:
encoded = mock.MagicMock()
encoded.value.decode = lambda x: msg
yield encoded
then I use that mock_consumer to create an instance of my object:
#pytest.fixture
#patch('service.producer.kafka_init')
def msg_producer_test1(mock_kafka_init):
pr = producer(config)
pr.balanced_consumer = mock_consumer('tests/test1.json')
return pr
and then I write test:
def test_that_we_process_message_correctly(msg_producer_test1):
msg_producer_test1.process()
assert (result = what_we_expect)
But I want to abstract it out so I will do something like:
def test_we_process_message_correctly(mock_msg_producer, 'tests/test1.json')
mock_msg_producer.process()
What would be best way to do that?

How to assign a value to a property in django

i have a new property in my model however I'd like to assign a test value in it for my test script.
this is my code:
models.py
mycode = models.UUIDField(null=True)
#property
def haveCode(self):
if self.mycode == uuid.UUID('{00000000-0000-0000-0000-000000000000}'):
return False
else
return True
and this is the test script that i am working on. I wanted to have a test value for haveCode:
test = Test()
test.mycode = uuid.UUID('{00000000-0000-0000-0000-000000000000}')
test.save()
checkTest = Test()
#this is only to pass the test
#delete this when start coding
checkTest.haveCode = True
assertEqual(test.haveCode, True)
however I got an error in checkTest.haveCode = True since this is just a property and not an attribute.
how to assign True to it? I appreciate your help
You can 'mock' that property using the mock library
from mock import patch, PropertyMock
#patch.object(Test, 'haveCode', new_callable=PropertyMock)
def myTest(test_haveCode_mock):
test_haveCode_mock.return_value = True
checkTest = Test()
assertEqual(checkTest.haveCode, True)
patch.stopall() # when you want to release all mocks

Categories