How to mock a function in another method being called using pytest - python

I have the following structure for the project
myapp
|
-contrib
|
- helpers.py
- views
|
- app_view.py
In helpers.py I have the following method
def method(**kwargs) -> int:
# Some code here
requests.post(
url=f"{REMOTE_SERVER}/sc",
json=kwargs,
timeout=5
)
In app_view.py I have the following implementation. I am calling the method inside a thread and I need to mock it using pytest to check whether it's being called once and called with certain number of parameters.
import myapp.contrib.helpers import method
class Score(MethodView):
#prepare_req
def post(self, **kwargs):
"""
Post Score
"""
response_data = dict()
# Some logic here
self.filter_score(kwargs)
return jsonify(response_data)
def filter_score(self, **kwargs)
thread = threading.Thread(target=method, kwargs=event_data)
thread.start()
Using pytest I am trying to mock the method() as follows.
def test_score(client, mocker):
url = "sc/1.1/score"
patched_method = mocker.patch(
"myapp.contrib.helpers.method"
)
client.post(url, json=issue_voucher_post_data)
patched_method.assert_called_once() # <- this is getting false and not mocking the method
Tried the below as well but it wont patch properly
patched_method = mocker.patch(
"myapp.views.app_view.method"
)
But it will call the corresponding view
How to mock a function used inside a thread in a view which is available in another module using pytest
?

Related

Pytest - mocking a side_effect on mock's nested attribute function / method

I have a fixture mocking an external library like so, using pytest-mock, which is a wrapper around unittest.mock.
# client.py
import Test as TestLibrary
class LibraryName():
def get_client():
return TestLibrary.Library()
# library_service.py
def using_library():
'''
Edited note: Library().attribute behind the scenes is set to
self.attribute = Attribute()
so this may be affecting the mocking
'''
client = LibraryName.get_client()
return client.attribute.method()
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
# test_library_service.py
def test_library_method(library_client_mock):
result = using_library()
I can mock a return value like so:
def test_library_method(library_client_mock):
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
result = using_library()
assert result == "test"
but I can't mock throwing an Exception with side_effect
def test_library_method(library_client_mock):
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError # doesn't work
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError() # doesn't work
attrs = { 'attribute.method.side_effect': TypeError }
library_client_mock.configure_mock(**attrs) # doesn't work
with pytest.raises(TypeError):
using_library() # fails assertion
what I missing here?
These are the errors in your code:
Change:
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
To:
library_client_mock.return_value.attribute.method.return_value = "test"
Change:
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError
To:
library_client_mock.return_value.attribute.method.side_effect = TypeError
Explanation
The .return_value must only be used for callable objects e.g. a function as documented:
return_value
Set this to configure the value returned by calling the mock:
>>> mock = Mock()
>>> mock.return_value = 'fish'
>>> mock()
'fish'
Thus, you can use .return_value only for the following:
TestLibrary.Library()
TestLibrary.Library().attribute.method()
But not for:
TestLibrary.Library().attribute
Because .attribute is not a callable e.g. TestLibrary.Library().attribute().
Warning
The way you are patching Library is via its source location at Test.Library (or aliased as TestLibrary.Library). specifically via:
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
It works currently because the way you import and use it is via the root path.
# client.py
import Test as TestLibrary
...
return TestLibrary.Library()
...
But if we change the way we imported that library and imported a local version to client.py:
# client.py
from Test import Library # Instead of <import Test as TestLibrary>
...
return Library() # Instead of <TestLibrary.Library()>
...
It will now fail. Ideally, you should patch the specific name that is used by the system under test, which here is client.Library.
import client
return mocker.patch.object(client, 'Library')
Unless you are sure that all files that will use the library will import only the root and not a local version.
#Niel Godfrey Ponciano set me on the right path with this syntax for the side_effect
library_client_mock.return_value.attribute.method.side_effect = TypeError
but it wasn't enough.
In
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
I had to add an extra mock:
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
mock_library_client = mocker.patch.object(TestLibrary, 'Library')
# option 1
mock_attribute = Mock()
# option 2, path to Library.attribute = Attribute()
mock_attribute = mocker.patch.object(TestLibrary.services, 'Attribute', autospec=True)
mock_library_client.attach_mock(mock_attribute, "attribute")
return mock_library_client
and then both of the following statements worked as expected. Although I am not sure why return_value works out of the box without an attached mock, but side_effect does not.
# return_value set correctly
# NOTE return_value needed after each
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
# side_effect set correctly
# NOTE return_value not needed after "attribute"
library_client_mock.return_value.attribute.method.side_effect = TypeError

pytest monkeypatch class method while calling super

I have a module loaders with class "Loader" with class method "load". During test, I want to append some additional steps to "Loader.load" to account for test specific data post-processing, so essentially overriding it. How do I properly do that?
I tried creating a mock class which inherits Loader and use monkeypatch.setattr("loaders.Loader", mock_loader), but this only works when I run one single test, but not when I run all tests.
loaders.py
class Loader:
def load():
# do something
return data
test.py
from loaders import Loader
class MockLoader(Loader):
def load():
data = super().load()
# do something to data
return data
def test_loader_special1(monkeypatch):
monkeypatch.setattr("loaders.Loader", MockLoader)
#run test logic 1
def test_loader_special2(monkeypatch):
monkeypatch.setattr("loaders.Loader", MockLoader)
#run test logic 2
Use patch or patch.object either via the builtin library unittest or the external library pytest-mock :
patch() acts as a function decorator, class decorator or a context
manager. Inside the body of the function or with statement, the target
is patched with a new object.
Where it is explicitly documented that the patch is only applicable per test:
When the function/with statement exits the patch is undone.
The patch will be used to wrap around your real implementation so that you can perform any necessary steps before and/or after calling it.
loaders.py
class Loader:
def load(self):
print("Real load() called")
return "real"
test_loaders.py
import pytest
from unittest.mock import patch
from loaders import Loader
#pytest.fixture
def mock_load(mocker):
real_func = Loader.load
def mock_func(self, *args, **kwargs):
print("Mock load() called")
data = real_func(self, *args, **kwargs)
data += " and mock"
return data
# Option 1: Using pytest-mock + new
mocker.patch.object(Loader, 'load', new=mock_func)
"""
Alternative ways of doing Option 1. All would just work the same.
# Option 2: Using pytest-mock + side_effect
mocker.patch.object(Loader, 'load', side_effect=mock_func, autospec=True)
# Option 3: Using unittest + new
with patch.object(Loader, 'load', new=mock_func):
yield
# Option 4: Using unittest + new
with patch.object(Loader, 'load', side_effect=mock_func, autospec=True):
yield
"""
def test_loader_special1(mock_load):
data = Loader().load()
print(f"{data=}")
assert data == "real and mock"
def test_loader_special2(mock_load):
data = Loader().load()
print(f"{data=}")
assert data == "real and mock"
def test_loader_special3():
data = Loader().load()
print(f"{data=}")
assert data == "real"
def test_loader_special4(mock_load):
data = Loader().load()
print(f"{data=}")
assert data == "real and mock"
Output:
$ pytest test_loaders.py -q -rP
.... [100%]
================================================================================================= PASSES ==================================================================================================
__________________________________________________________________________________________ test_loader_special1 ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Mock load() called
Real load() called
data='real and mock'
__________________________________________________________________________________________ test_loader_special2 ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Mock load() called
Real load() called
data='real and mock'
__________________________________________________________________________________________ test_loader_special3 ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Real load() called
data='real'
__________________________________________________________________________________________ test_loader_special4 ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Mock load() called
Real load() called
data='real and mock'
4 passed in 0.01s
Modifying the answer https://stackoverflow.com/a/68675071/14536215 to use monkeypatch:
#pytest.fixture
def mock_load(monkeypatch):
real_func = Loader.load
def mock_func(self, *args, **kwargs):
print("Mock load() called")
data = real_func(self, *args, **kwargs)
data += " and mock"
return data
monkeypatch.setattr(Loader, "load", mock_func)
You can then mark the fixture to be loaded for all tests in the testing module with:
#pytest.fixture(autouse=True)
or mark the tests to use specified fixtures with:
#pytest.mark.usefixtures("mock_load")
def test_loader_special1():
...
Edit:
If you want to mock the whole class, you need to mock it before you import the class for it to take effect, or you could just import the module so you don't have the import statements sprinkled around:
import loaders
class MockLoader(loaders.Loader):
def load(self):
data = super().load()
data += " and mock"
return data
def test_loader_special1(monkeypatch):
monkeypatch.setattr("loaders.Loader", MockLoader)
data = loaders.Loader().load()
print(f"{data=}")
assert data == "real and mock"

Mock requests.post

I am using pytest to test a method that calls requests.post. Is there a easy way and preferably without third party libraries to do this?
class Dispatcher:
def __init__(self, url):
self.url = url
self.session = None
def dispatch(self):
return self.session.post(self.url).json()
def test_dispatch():
d = Dispatcher(url="")
d.session = # ... here, how can I mock the return value of json()?
result = d.dispatch()
So this is a pretty straightforward example. We want to set session to be a MagicMock object.
from unittest.mock import MagicMock
def test_dispatch():
expected = {"fizz": "buzz"}
mock_session = MagicMock()
mock_session.configure_mock(
**{
"post.return_value": mock_session,
"json.return_value": expected
}
)
d = Dispatcher(url="")
d.session = mock_session
result = d.dispatch()
assert result == expected
Since Mock objects return a brand new mock object when methods are called on them (without being configured), we have to configure the object as such. If we didn't configure post to return the original mock we have, then it would return a brand new mock object and our test would fail. Conversely you can configure another Mock object to be the return value of post and configure that object, but I prefer this approach.
Explanation showing the call is listed below.
self.session is our mock_session object we created
self.session.post(arg, **kwargs) returns mock_session
mock_session.json() returns the dictionary we specified

Python mock a method when specific argument

I have a python method like
import external_object
from external_lib1 import ExternalClass1
from external_lib2 import Hook
class MyClass(self):
def my_method(self):
ExternalClass.get('arg1') #should be mocked and return a specific value with this arg1
ExternalClass.get('arg2') #should be mocked and return a specific value with this arg2
def get_hook(self):
return Hook() # return a mock object with mocked method on it
def my_method(self):
object_1 = external_object.instance_type_1('args') # those are two different object instanciate from the same lib.
object_2 = external_object.instance_type_2('args')
object_1.method_1('arg') # should return what I want when object_1 mocked
object_2.method_2 ('arg') # should return what I want when object_2 mocked
In my test I would like to realise what I put in comments.
I could manage to do it, but every time it gets really messy.
I use to call flexmock for some stuff (by example ExternalClass.get('arg1') would be mock with a flexmock(ExternalClass).should_return('arg').with_args('arg') # etc...) but I'm tired of using different test libs to mock.
I would like to use only the mock library but I struggle to find a consistent way of doing it.
I like to use python's unittest lib. Concretely the unittest.mock which is a great lib to customize side effects and return value in unit tested functions.
They can be used as follows:
class Some(object):
"""
You want to test this class
external_lib is an external component we cannot test
"""
def __init__(self, external_lib):
self.lib = external_lib
def create_index(self, unique_index):
"""
Create an index.
"""
try:
self.lib.create(index=unique_index) # mock this
return True
except MyException as e:
self.logger.error(e.__dict__, color="red")
return False
class MockLib():
pass
class TestSome(unittest.TestCase):
def setUp(self):
self.lib = MockLib()
self.some = Some(self.lib)
def test_create_index(self):
# This will test the method returns True if everything went fine
self.some.create_index = MagicMock(return_value={})
self.assertTrue(self.some.create_index("test-index"))
def test_create_index_fail(self):
# This will test the exception is handled and return False
self.some.create_index = MagicMock(side_effect=MyException("error create"))
self.assertFalse(self.some.create_index("test-index"))
Put the TestSome() class file somewhere like your-codebase-path/tests and run:
python -m unittest -v
I hope it's useful.

How do I mock a method that uses requests.get in my class?

I'm attempting to create a few unit tests for my class. I want to mock these, so that I don't burn through my API quota running some of these tests. I have multiple test cases that will call the fetch method, and depending on the passed URL I'll get different results back.
My example class looks like this:
import requests
class ExampleAPI(object):
def fetch(self, url, params=None, key=None, token=None, **kwargs):
return requests.get(url).json() # Returns a JSON string
The tutorial I'm looking at shows that I can do something like this:
import unittest
from mock import patch
def fake_fetch_test_one(url):
...
class TestExampleAPI(unittest.TestCase):
#patch('mymodule.ExampleAPI.fetch', fake_fetch_test_one)
def test_fetch(self):
e = ExampleAPI()
self.assertEqual(e.fetch('http://my.api.url.example.com'), """{'result': 'True'}""")
When I do this, though, I get an error that says:
TypeError: fake_fetch_test_one() takes exactly 1 argument (3 given)
What is the proper way to mock a requests.get call that is in a method in my class? I'll need the ability to change the mock'd response per test, because different URLs can provide different response types.
Your fake fetch needs to accept the same arguments as the original:
def fake_fetch(self, url, params=None, key=None, token=None, **kwargs):
Note that it's better to mock just the external interface, which means letting fetch call requests.get (or at least, what it thinks is requests.get):
#patch('mymodule.requests.get')
def test_fetch(self, fake_get):
# It would probably be better to just construct
# a valid fake response object whose `json` method
# would return the right thing, but this is a easier
# for demonstration purposes. I'm assuming nothing else
# is done with the response.
expected = {"result": "True"}
fake_get.return_value.json.return_value = expected
e = ExampleAPI()
self.assertEqual(e.fetch('http://my.api.url.example.com'), expected)
from you test method you can monkeypatch your requests module
import unittest
class Mock:
pass
ExampleAPI.requests = Mock()
def fake_get_test_one(url):
/*returns fake get json */
ExampleAPI.requests.get= Mock()
ExampleAPI.requests.json = fake_get_test_one
class TestExampleAPI(unittest.TestCase):
def test_fetch(self):
e = ExampleAPI()
self.assertEqual(e.fetch('http://my.api.url.example.com'), """{'result': 'True'}""")
you can setup the patch in each setup() and corresponding teardown() methods of your test class if needed

Categories