pytest-mock assert_called_with failed for class function - python

I am planning to use pytest and pytest-mock for validating the Python code. Being a newbie, wrote a sample code to validate the mock on class and seeing failure. I am wondering what went wrong.
src/main.py
class Main(object):
def __init__(self, my_var=None):
self.var = my_var
def internal_func(self, var=10):
my_var = var + 20
return my_var
def test_func(self):
val = self.internal_func(20)
return val + 40
tests/test_main.py
import pytest
from pytest_mock import mocker
from src.main import Main
def new_func(cls, *args, **kwargs):
return 2
def test_main_mock(mocker):
mocker.patch.object(Main, 'internal_func')
val = Main().test_func()
assert Main.internal_func.assert_called_with(20)
It fails with the following error
======================================================================================== FAILURES ========================================================================================
_____________________________________________________________________________________ test_main_mock _____________________________________________________________________________________
mocker = <pytest_mock.MockFixture object at 0x7f34f490d8d0>
def test_main_mock(mocker):
mocker.patch.object(Main, 'internal_func')
main = Main()
val = main.test_func()
# assert val == 80
> assert Main.internal_func.assert_called_with(20)
E AssertionError: assert None
E + where None = <bound method MagicMock.wrap_assert_called_with of <MagicMock name='internal_func' id='139865418160784'>>(20)
E + where <bound method MagicMock.wrap_assert_called_with of <MagicMock name='internal_func' id='139865418160784'>> = <MagicMock name='internal_func' id='139865418160784'>.assert_called_with
E + where <MagicMock name='internal_func' id='139865418160784'> = Main.internal_func
tests/test_main.py:13: AssertionError

The return_value or side_effect must be set before the patched func take effect
def test_main_mock(mocker):
# mock internal_func of class Main
mocked_internal_func = mocker.patch.object(Main, 'internal_func')
# assign return_value or side_effect
mocked_internal_func.return_value = -10
# class instance
ma = Main()
val = ma.test_func()
assert ma.internal_func.assert_called_with(20)
Correction of mistake, the assert should not be used together with assert_called_with, they are independent assert.
assert val == 30
mocked_internal_func.assert_called
ma.internal_func.assert_called_with(20)
mocked_internal_func.assert_called_with(20)

Related

Pytest monkeypatching a decorated method, it also replaces the decorator

While I was testing some code with pytest, I needed to monkeypatch a method that was also decorated. I patched the method and while I expected the decorators being executed in the patched method, they weren't, so it looks like the patched method replaces everything.
Is this the normal behaviour?
I wrote a simplified example of the problem:
functions.py
def mul_by(func):
def wrapper(num):
return func(num * 4)
return wrapper
#mul_by
def show_number(n: int):
print(n)
return n
test_functions.py
import sys
from src.functions import show_number
def test_show_number(monkeypatch):
def mocked_show_number(num: int):
print('Mocked func')
return 5
monkeypatch.setattr(sys.modules[__name__], 'show_number', mocked_show_number)
assert show_number(2) == 20
Output
collected 1 item
test/test_functions.py F [100%]
================================================================================================= FAILURES =================================================================================================
_____________________________________________________________________________________________ test_show_number _____________________________________________________________________________________________
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x10b7e5d00>
def test_show_number(monkeypatch):
def mocked_show_number(num: int):
print('Mocked func')
return 5
monkeypatch.setattr(sys.modules[__name__], 'show_number', mocked_show_number)
> assert show_number(2) == 20
E assert 5 == 20
E + where 5 = show_number(2)
test/test_functions.py:11: AssertionError
------------------------------------------------------------------------------------------- Captured stdout call -------------------------------------------------------------------------------------------
Mocked func
========================================================================================= short test summary info ==========================================================================================
FAILED test/test_functions.py::test_show_number - assert 5 == 20
The decorator simply multiplies the number by 4, and in the monkeypatched method I hardcode the value just to return 5, so I'd expect 20 in the result. But, 5 is returned, the monkeypatched method's value.
Can someone point if there is any error in this way to monkeypatch or if it is the desired behaviour?

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.

Patching a method of a class that is already patched

I'm trying to patch a class and its method, this is an example that what I'm trying to do:
class Car:
def __init__(self, color):
self.color = color
def show_color(self):
return self.color
I have this class and I want to patch the class, and its method separately, I didn't create a fake class, because in my case the class is complex, so I want the class to be created and then just patch a method of some that has the class, I'm trying to do this:
import example
def test_example(
mocker
):
mocker.patch("example.Car")
mocker.patch("example.Car.show_color").return_value = "red"
c = example.Car("blue")
assert c.show_color() == "red"
Also, I tried to do something like this:
import example
def test_example(
mocker
):
mocker.patch("example.Car").return_value = mocker.create_autospec(
example.Car,
show_color = "red"
)
c = example.Car("blue")
assert c.show_color() == "red"
In the two cases, I got this error:
========================================================================================== FAILURES ==========================================================================================
________________________________________________________________________________________ test_example ________________________________________________________________________________________
mocker = <pytest_mock.plugin.MockerFixture object at 0x102281bb0>
def test_example(
mocker
):
mocker.patch("example.Car")
mocker.patch("example.Car.show_color").return_value = "red"
c = example.Car("blue")
> assert c.show_color() == "red"
E AssertionError: assert <MagicMock name='Car().show_color()' id='4331364896'> == 'red'
E + where <MagicMock name='Car().show_color()' id='4331364896'> = <MagicMock name='Car().show_color' id='4331344512'>()
E + where <MagicMock name='Car().show_color' id='4331344512'> = <MagicMock name='Car()' id='4331304944'>.show_color
test_mocking.py:21: AssertionError
================================================================================== short test summary info ===================================================================================
FAILED test_mocking.py::test_example - AssertionError: assert <MagicMock name='Car().show_color()' id='4331364896'> == 'red'
===================================================================================== 1 failed in 0.06s ======================================================================================
I'm patching the method, because I have another method from another class that I want to test
mocker.patch itself returns the mock, which you can then interact with:
def test_example(mocker):
Car = mocker.patch("example.Car")
Car.return_value.show_color.return_value = "red"
c = example.Car("blue")
assert c.show_color() == "red"
Alternatively, patch accepts **kwargs which you can use to configure sub-mocks:
def test_example_alternative(mocker):
config = {"return_value.show_color.return_value": "red"}
mocker.patch("example.Car", **config)
c = example.Car("blue")
assert c.show_color() == "red"
You’re patching with:
show_color = "red"
But your assert is trying to call show_color as if it were a function
if c.show_color() == “red”:
which is doomed to fail, maybe you should test:
if c.show_color == “red”:

Unittest Mock - Assert a mock object is not called

I am new to unittest and mock in python. I would like to assert a mock is not called.
def test_check_is_available_all_day(self):
create_not_on_leave = Mock()
delete_not_on_leave = Mock()
create_is_not_available = Mock()
for idx, row in self.employee.iterrows():
if row['all_day'] == 1:
if row['is_available'] == 1:
create_not_on_leave()
create_not_on_leave.assert_called()
elif row['is_available'] == 0:
delete_not_on_leave()
create_is_not_available()
How do I assert delete_not_on_leave() is not called. I have tried assert delete_not_on_leave.assert_called() == False but this is gave a error.
use: self.assertEqual(create_not_on_leave.call_count, 0)

Categories