Python unittest change mock patch value on fly - python

I'm having trouble while dealing with patching. I'm using mock from unittest library. While testing check_codes() view I would like to set another values to db.find_one()
api.utils.py
from pymongo import MongoClient
import os
def get_share_code_collection():
client = MongoClient(os.getenv("DB_HOST"))
db_handle = client[os.getenv("DB_NAME")]
return db_handle["share_codes"]
views.py
def check_codes(self, request):
db = get_share_code_collection()
data = db.find_one({"specialist_id": {"$exists": True}})
test_views.py
from unittest import mock
#mock.patch("api.utils.get_share_code_collection")
def test_share_code_correct_no_share_types(
self, mocked_collection, mocked_share_code, user_model
):
mocked_collection().find_one.return_value = True
...
#mock.patch("api.utils.get_share_code_collection")
def test_share_code_no_start_time(
self, mocked_collection, user_model
):
mocked_collection().find_one.return_value = False
...
The only workaround I found is setting
mocked_collection().find_one.side_effect = [True,False]
but once it is initialized I can't add values. How can I deal with the problem?

Related

mocking a class instance in pytest

am trying to mock a class instance while testing a method something like below
source
main_proc.py
devinstance.py
prodinstance.py
requirements.txt
host.json
main_proc.py
def get_instance(self)
ins = None
env = os.getenv('env', 'dev')
if env == 'dev':
ins = DevInstance()
else:
ins = ProdInstance()
return ins
Sample DevInstance class
devinstance.py
class DevInstance:
def __init__(self):
self.eh_client = dict()
self.initialize()
def initialize(self):
try:
client = EventHubProducerClient.from_connection_string(conn_str=self.secrets_dict[value],
eventhub_name=names[i], http_proxy=HTTP_PROXY)
except Exception as e:
logging.error(e)
raise e
testing the get instance like below as my intension is to mock the entire DevInstance class obj. both files are in the same module.
#mock.patch("devinstance.DevInstance")
def test_get_instance(self, devins):
# Act
devins.return_value = MagicMock()
result = get_instance()
# Assert
assert result is not None
Can anyone help me how this can be acheived?
You need to patch where the object is being looked up (see the Where to patch documentation).
If you're testing get_instance in main_proc, then you need to patch where DevInstance is imported in main_proc.
For example, if you're importing it with from devinstance import DevInstance, then you need to patch it with #mock.patch("main_proc.DevInstance").
Otherwise, if you're importing it with import devinstance, then you need to patch it with #mock.patch("main_proc.devinstance.DevInstance").

replace python class with mocked class

I am new to pytest.
I am trying to mock/replace my client.py with fake_client.py for the testing.
The fake_client class contains the same methods as the client class.
Here is my project structure and code:-
abc/base_class.py
from .client import Client
class PushBase:
def __init__(self, hostname):
self.client = Client(hostname)
def process_product(self, item): # item type is dict {}
product_id = item.get('product_id')
if item.get('state') == 'present':
if not product_id:
# creating product
product_id = self.client.create_product(item.get('data'))
return product_id
# update product
self.client.update_product(item.get('data'))
elif item.get('state') == 'absent':
# delete product
self.client.delete_product(product_id)
This is my client.py with API calls
in abc/client.py
class Client:
def __init__(self, hostname):
self.hostname = hostname
# some other stuff
def create_product(self, params=None):
# some code
def update_product(self, params=None):
# some code
def delete_product(self, params=None):
# some code
I have created a fake client to test against the actual client.py
and it has the same methods as the client.py
in tests/fake_client.py
class FakeClient:
def __init__(self, *args, **kwargs):
pass
def create_product(self):
# some code
def update_product(self):
# some code
def delete_product(self):
# some code
in tests/test_base_class.py
from tests.fake_client import FakeClient
import unittest
from abc.base_class import BaseClass
import pytest
try:
import mock
except ImportError:
from unittest import mock
class TestBaseClassOperations(unittest.TestCase):
def setUp(self):
self.push_base = BaseClass("http://fake_host_nmae/test", "foo", "bar")
self.push_base.client = mock.patch('abc.base_class.Client', new=FakeClient()).start()
def test_create_valid_product(self):
product_dict = { # some stuff }
created_product_id = self.push_base.process_product(product_dict)
# process_product() will call create_product from fake client
# will return 1 if FakeClient().create_product() called
assert created_product_id == 1
I tried it another way.
#pytest.fixture
def fixture_product_creation():
return { # product fixture
}
#mock.patch('abc.base_class.Client', return_value=FakeClient())
class TestBaseClassAgain:
def test_create_valid_product(self, mock_client, fixture_product_creation):
push_base = BaseClass("http://fake_host_nmae/test", "foo", "bar")
created_product_id = push_base.process_product(fixture_product_creation)
expected = 1
assert created_product_id == expected
# how can I use this mock_client here?
Although I can replace the client with the FakeClient, but I am unsure how to arrange all the mock things to get it tested with the assert or assert_called_with calls.
I referred this but not able to arrange it in a proper pythonic way.
Can anyone help me rearrange this and suggest to me any better way to replace the client class with the fake client class by using pytest mock?
Thanks.
In order to properly test this you should make a change to your PushBase class. It is not good practice to instantiate a dependent object in the __init__ method of your class, instead consider passing the object in. This makes testing easier as you can just inject the dependency as needed. Another option would be to make a #classmethod that instantiates the object with the client. In the code below I illustrate how to do the former.
It also appears you have an indentation error as the update_product method can never be called based on the logic you currently have.
# base.py
class PushBase:
def __init__(self, client):
self.client = client
def process_product(self, item): # item type is dict {}
product_id = item.get('product_id')
if item.get('state') == 'present':
if not product_id:
# creating product
product_id = self.client.create_product(item.get('data'))
return product_id
# update product
self.client.update_product(item.get('data'))
elif item.get('state') == 'absent':
# delete product
self.client.delete_product(product_id)
# test_base.py
import pytest
from src.base import PushBase
def test_create_product_id(mocker):
mock_client = mocker.MagicMock()
base = PushBase(mock_client)
item = {
"state": "present",
"data": "fizz"
}
mock_client.create_product.return_value = "ok"
product_id = base.process_product(item)
assert product_id == "ok"
mock_client.create_product.assert_called_once_with("fizz")
def test_update_product(mocker):
mock_client = mocker.MagicMock()
base = PushBase(mock_client)
item = {
"state": "present",
"data": "bang",
"product_id": "baz"
}
base.process_product(item)
mock_client.update_product.assert_called_once_with("bang")
def test_delete_product(mocker):
mock_client = mocker.MagicMock()
base = PushBase(mock_client)
item = {
"state": "absent",
"product_id": "vroom"
}
base.process_product(item)
mock_client.delete_product.assert_called_once_with("vroom")
============================================== test session starts ===============================================
platform darwin -- Python 3.8.9, pytest-7.0.1, pluggy-1.0.0
rootdir: ***
plugins: asyncio-0.18.3, hypothesis-6.48.1, mock-3.7.0
asyncio: mode=strict
collected 3 items
tests/test_base.py ...
=============================================== 3 passed in 0.01s ================================================
I am using the pytest-mock package, which is where the mocker fixture comes from. The nice thing about being able to inject a dependency into your class is you don't need to configure all the methods beforehand, you can modify what you need within each test function. There are improvements you can make to the tests above, but that exercise is left to you. Hopefully this should help you understand the direction you should go in.

Python Mock patch ldap3 search response

I am trying to mock the below function but I'm not sure how to mock the Connection response:
def get_user_res(user, pass):
res = None
server = Server('my_server')
connnection = Connection(server, user, pass, strategy=SAFE_SYNC, auto_bind=True)
if connection.bind():
connection.search(search_base, search_filter, SUBTREE)
res = connection.response
connection.unbind()
return res
#mock.patch("ldap3.Server")
#mock.patch("ldap3.Connection.response")
def test_get_user_res(mock_connection, mock_server):
mock_connection.return_value = ""
retrived_res = get_user_res("fake_user","fake_password")
expected_res = ""
assert retrived_res == expected_res
The root problem is that you're mocking the wrong things. If you have a file named ldapclient.py that contains your get_user_rest method, like this (note that I've rewritten things a bit to make our lives easier when writing tests):
import ldap3
server = ldap3.Server('my_server')
search_base = 'dc=example, dc=com'
def get_user_res(user, password, search_filter=None):
res = None
connection = ldap3.Connection(
server, user, password,
client_strategy=ldap3.SAFE_SYNC, auto_bind=True)
if connection.bind():
res = connection.search(search_base, search_filter, ldap3.SUBTREE)
connection.unbind()
return res
Then what you need to mock is the ldap3.Connection class. But since your test is in a different module, you'll need to call #mock.patch('ldapclient.ldap3.Connection), assuming that your test is defined like this:
import ldap3
from unittest import mock
import ldapclient
#mock.patch("ldapclient.ldap3.Connection")
def test_get_user_res(mock_connection_class):
mock_connection = mock.Mock()
mock_connection.search.return_value = 'fake_return'
mock_connection_class.return_value = mock_connection
retrived_res = ldapclient.get_user_res("fake_user", "fake_password")
expected_res = "fake_return"
assert retrived_res == expected_res
There are a few things to note here:
As mentioned earlier, because we have import ldapclient, we need to mock ldapclient.ldap3.Connection.
We make the ldap3.Connection class return a new mock.Mock object, since we want to be able to mock methods on the object returned when calling connection = ldap3.Connection(...)
We make the search method return a fake value so that we can ensure it gets called as expected.

Pytest Mock AWS SecurityManager

my project has a file called config.py which has, among others, the following code:
class Secret(Enum):
DATABASE_A = 'name_of_secret_database_A'
DATABASE_A = 'name_of_secret_database_A'
def secret(self):
if self.value:
return get_secret(self.value)
return {}
def get_secret(secret_name):
session = Session()
client = session.client(
service_name='secretsmanager',
region_name='us-east-1',
)
secret_value = client.get_secret_value(SecretId=secret_name)
return loads(secret_value.get('SecretString', "{}"))
I need to somehow mock get_secret in tests with pytest for all enum calls, for example Secret.DATABASE_A.secret ()
You can use monkeypatch to override the behaviour of get_secret(). I have made the get_secret() method a static method of the Secret class, but you can make it part of any module you want and import it as well. Just make sure you change in in the monkeypatch.setattr() call as well.
import pytest
from enum import Enum
class Secret(Enum):
DATABASE_A = 'name_of_secret_database_A'
DATABASE_B = 'name_of_secret_database_B'
def secret(self):
if self.value:
return Secret.get_secret(self.value)
return {}
#staticmethod
def get_secret(secret_name):
session = Session()
client = session.client(
service_name='secretsmanager',
region_name='us-east-1',
)
secret_value = client.get_secret_value(SecretId=secret_name)
return loads(secret_value.get('SecretString', "{}"))
def test_secret_method(monkeypatch):
def get_secret(secret_name):
return "supersecret"
monkeypatch.setattr(Secret, "get_secret", get_secret)
s = Secret.DATABASE_A
assert s.secret() == "supersecret"
This returns into 1 passed test.
What is happening here is, that I created a function get_secret() in my test_secret_method as well, and then overwrite the Secret.get_secret() with that new method. Now, you can use the Secret class in your test_method and be sure what the 'get_secret()' method will return without actually running the original code.

python mock global function that is used in class

I can't seem to get my head around mocking in Python. I have a global function:
a.py:
def has_permission(args):
ret_val = ...get-true-or-false...
return ret_val
b.py:
class MySerializer(HyperlinkedModelSerializer):
def get_fields():
fields = super().get_fields()
for f in :
if has_permission(...):
ret_val[f.name] = fields[f]
return ret_val
c.py:
class CountrySerializer(MySerializer):
class Meta:
model = Country
Question: Now i want to test c.py, but i want to mock the has_permission function that is defined in a.py, but is called in the get_fields-method of the class MySerializer that is defined in b.py ... How do i do that?
I've tried things like:
#patch('b.MySerializer.has_permission')
and
#patch('b.MySerializer.get_fields.has_permission')
and
#patch('a.has_permission')
But everything i try either just doesn't work and has_permission is still executed, or python complains about that it can't find the attribute 'has_permission'
with the patching done in:
test.py
class TestSerializerFields(TestCase):
#patch(... the above examples....)
def test_my_country_serializer():
s = CountrySerializer()
self..assertTrue(issubclass(my_serializer_fields.MyCharField, type(s.get_fields()['field1'])))
You need to patch the global in the b module:
#patch('b.has_permission')
because that's where your code looks for it.
Also see the Where to patch section of the mock documentation.
You need to patch the method where it exists at the time your test runs. If you try and patch the method where it is defined after the test code has already imported it, then the patch will have no effect. At the point where the #patch(...) executes, the test code under test has already grabbed the global method into its own module.
Here is an example:
app/util/config.py:
# This is the global method we want to mock
def is_search_enabled():
return True
app/service/searcher.py:
# Here is where that global method will be imported
# when this file is first imported
from app.util.config import is_search_enabled
class Searcher:
def __init__(self, api_service):
self._api_service = api_service
def search(self):
if not is_search_enabled():
return None
return self._api_service.perform_request('/search')
test/service/test_searcher.py:
from unittest.mock import patch, Mock
# The next line will cause the imports of `searcher.py` to execute...
from app.service.searcher import Searcher
# At this point, searcher.py has imported is_search_enabled into its module.
# If you later try and patch the method at its definition
# (app.util.config.is_search_enabled), it will have no effect because
# searcher.py won't look there again.
class MockApiService:
pass
class TestSearcher:
# By the time this executes, `is_search_enabled` has already been
# imported into `app.service.searcher`. So that is where we must
# patch it.
#patch('app.service.searcher.is_search_enabled')
def test_no_search_when_disabled(self, mock_is_search_enabled):
mock_is_search_enabled.return_value = False
mock_api_service = MockApiService()
mock_api_service.perform_request = Mock()
searcher = Searcher(mock_api_service)
results = searcher.search()
assert results is None
mock_api_service.perform_request.assert_not_called()
# (For completeness' sake, make sure the code actually works when search is enabled...)
def test_search(self):
mock_api_service = MockApiService()
mock_api_service.perform_request = mock_perform_request = Mock()
searcher = Searcher(mock_api_service)
expected_results = [1, 2, 3]
mock_perform_request.return_value = expected_results
actual_results = searcher.search()
assert actual_results == expected_results
mock_api_service.perform_request.assert_called_once_with('/search')

Categories

Resources