python mock find where method was called - python-3.x

I am working on a project that has some pytests, in one of the tests I have the following line:
mocked_class = Mock()
assert mocked_class.send.call_count == 1
Now I can not find the place in code where someone is calling the send method.
I tried to add
mocked_class.send=my_method
and added prints on that or put breakpoint, but it did not work.
So it seems that I am missing something
The tests are working on python 3.8
with
import pytest
from mock import Mock
How can I find who calls this method?
Any other help of debuging this

send might not exist in your code since its part of a Mock instance and you can call arbitrary methods from a Mock instance:
from unittest.mock import Mock
def test_1():
mocked_class = Mock()
mocked_class.wasda()
assert mocked_class.wasda.call_count == 1
test_1()
There are plenty of examples in the docs: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock
Your code should also raise an "AssertionError" since there are no calls to send before the assertions statement.

Related

Class wide mock in pytest (for all methods in the whole TestClass)

I am unittesting my new librabry, which is basically database interface. Our apps use it to access our database. that means, I want to test all methods, but I do not want DB commands to be called for real. I only check if they are called with correct arguemnts.
For that purpose, I am mocking the database library. this is the actual code that DOES work:
import pytest
from unittests.conftest import PyTestConfig as Tconf
from my_lib.influx_interface import InfluxInterface
class TestInfluxInterface:
def test_connect(self, mocker):
"""
Create InfluxConnector object and call connect()
check if __init__() arguments are passed / used correctly
"""
influx_client = mocker.patch('my_lib.influx_interface.InfluxDBClient', autospec=True)
test_connector = InfluxInterface(Tconf.test_id)
# Call connect with no input (influx_client - should be called with no arguemnts
test_connector.connect()
influx_client.assert_called_once()
influx_client.reset_mock()
# Call connect with custom correct input (influx_client - should be called with custom values
test_connector.connect(Tconf.custom_conf)
influx_client.assert_called_once_with(url=Tconf.custom_conf["url"], token=Tconf.custom_conf["token"],
org=Tconf.custom_conf["org"], timeout=Tconf.custom_conf["timeout"],
debug=Tconf.custom_conf["debug"])
influx_client.reset_mock()
# Call connect with incorrect input (influx_client - should be called with default values
test_connector.connect(Tconf.default_conf)
influx_client.assert_called_once_with(url=Tconf.default_conf["url"], token=Tconf.default_conf["token"],
org=Tconf.default_conf["org"], timeout=Tconf.default_conf["timeout"],
debug=Tconf.default_conf["debug"])
Now, what I do next, is to add more methods into TestInfluxInterface class, which will be testing rest of the code. One test method for each method in my library. Thats how I usually do it.
The problem is, that there is a part of the code:
influx_client = mocker.patch('my_lib.influx_interface.InfluxDBClient', autospec=True)
test_connector = InfluxInterface(Tconf.test_id)
That will be same for every method. Thus I will be copy-pasting it over and over. As you can already see, thats not good solution.
In unittest, I would do this:
import unittest
import unittest.mock as mock
from unittests.conftest import PyTestConfig as Tconf
from my_lib.influx_interface import InfluxInterface
#mock.patch('my_lib.influx_interface.InfluxDBClient', autospec=True)
class TestInfluxInterface:
def setUp(self):
self.test_connector = InfluxInterface(Tconf.test_id)
def test_connect(self, influx_client):
"""
Create InfluxConnector object and call connect()
check if __init__() arguments are passed / used correctly
"""
# Call connect with no input (influx_client - should be called with no arguemnts
self.test_connector.connect()
influx_client.assert_called_once()
Than in each method, I would use self.test_connector to call whatever method I want to test, and check if it called correct influx_client method, with correct parameters.
However, we are moving from unittest to pytest. And while I am wrapping my head around pytest docs, reading stuff here and there, fixtures, mockers, etc..I cannot find out how to do this correctly.

Same mock object has different ids in test class and testable class

I want to write unit tests for the main.py class. The file structure of my project is like this,
my unit tests are included in test/source/code_files folder. I want to mock some methods in the main class too. (which uses variables in source/config/config.py) I'm using the patch for this.
ex:
import main
#patch('main.config.retry_times')
def test_method(self, mock_retry_times):
mock_retry_times().return_value = 'retry_times_mock_val'
#calling the main class method
in the main class method, retry_times is defined like this,
from source.config import config
def method():
var1 = config.retry_times['url']
# Do other stuff
This gave me an error as,
I tried with Magic Mock object as well. But it didn't work as per this solution. My imports also work fine.
But I figure out one thing.
when I check mock IDs in both the testable class and the test class they were different like this.
It seems like an issue in these ids. I think they must be the same in both classes. Does anyone help me to sortout this issue.

monkeypatch in python, leaking mocks to other tests causing them to fail

I am monkeypatching the other function calls while writing pytest unittests as below:
from _pytest.monkeypatch import MonkeyPatch
from third_party import ThirdParty
def test_my_func():
resp1= "resp1"
monkeypatch = MonkeyPatch()
def mock_other_method(*args, **kwargs):
return resp1
monkeypatch.setattr(ThirdParty, "other_method", mock_other_method)
assert ThirdParty().other_method() == "resp1"
# Some assertions
def test_my_func2():
monkeypatch = MonkeyPatch()
expected_result = "This is expected"
result_third_party = ThirdParty().other_method()
assert result_third_party == expected_result
where,
third_party.py has:
class ThirdParty:
def other_method(self):
return "This is expected"
These tests when ran independently run fine (I just wrote it, so there might be some syntax error). But when I run it as pytest -v, 2nd test will fail. The reason is that on calling other_method, it will return the mocked method: mock_other_method, and since the response is different it will fail. Please suggest some solution to this
monkeypatch is a pytest fixture and as such
not supposed to be imported. Instead, you have to provide it as an argument in the test functions. Pytest loads all fixtures at test start and looks them up by name, so the correct usage would be:
from third_party import ThirdParty
# no import from pytest internal module!
def test_my_func(monkeypatch):
resp1 = "resp1"
def mock_other_method(*args, **kwargs):
return resp1
monkeypatch.setattr(ThirdParty, "other_method", mock_other_method)
assert ThirdParty().other_method() == resp1
The monkeypatch fixture has function scope, meaning that the patching will be reverted after each test function automatically.
Note that using the internal pytest API (e.g. importing _pytest) is discouraged, both because it may change with a new version, and because there are more convenient and secure methods to use the features (and not last because these are documented). You should never bother about fixture cleanup yourself, if you use a fixture provided by pytest or a pytest plugin - it would be too easy to forget the cleanup and get unwanted side effects.
I found the solution by adding monkeypatch.undo() at the end of every test. This will prevent the monkeypatch() from leaking into other functions

Is there a way to assert that an attribute has been set on a unittest mock object?

In the following code, is there a way to assert that the query attribute has been set if search is a mock object? Or if query was the mock object, is there a way to do it?
search.query = Q('bool', must=must)
So far I've found out that Python unittest.mock only supports asserting that mocks have been called as functions. Also the setattr magic method can not be mocked so search.__setattr__ can not be used to assert the above.
Maybe I misunderstood your point, but why not simply run the code under test and afterwards check that search has got that expected attribute query and that search.query holds the expected value? For example (I assigned search a MagicMock object because according to your description it probably has to be. For the sake of the following example it would not be necessary that search is a MagicMock or any kind of mock):
# setup
search = MagicMock()
# when exercised, your code under test would do:
search.query = 42
# afterwards you validate that search.query holds the value you expect:
self.assertTrue(hasattr(search, 'query'))
self.assertEqual(search.query, 42)

Asyncio testing a function which uses loop.call_soon_threadsafe

I have the following function:
def send_command(self, cmd):
self.loop.call_soon_threadsafe(
functools.partial(
self._transport.write, str(cmd).encode() + b"\n"
)
)
The system under test (sut) is a class inheriting from asyncio.Protocol which sends some commands to a piece of hardware on a socket. I have to use threads since this is part of a GUI under wxPython. Finally, if I call self._transport.write the code works fine on Linux but crashes on Windows™.
When running the test:
#pytest.mark.asyncio
async def test_send_command(self):
self.sut._transport = Mock()
self.sut.send_command("ook eek")
assert self.sut._transport.write.called is True
I get an assert error. The self.sut._transport.write is never called. If I call self._transport.write directly in the function, the code crashes on Windows™ but the test passes just fine.
What am I missing here?
Anyone?…
Surely, this is not such an edge case…
A work around…
After reading and experimenting with even loops, using this:
import asyncio
import selectors
selector = selectors.SelectSelector()
loop = asyncio.SelectorEventLoop(selector)
asyncio.set_event_loop(loop)
by passes the problem. Of course, it means using a sub-efficient loop on Windows. ☹ PHA!
Anyone with a better solution is welcome to some fake internet points.

Resources