Is it possible to mock builtins.hasattr? [duplicate] - python

When running the test bellow, I got a stop called on unstarted patcher.
def test_get_subvention_internal_no_triggered_admission(self):
billing_cluster = BillingClusterFactory()
subvention = SubventionFactory(billing_cluster=billing_cluster)
convive_sub = ConviveFactory(subvention=subvention, billing_cluster=billing_cluster)
order_5 = OrderFactory(beneficiary=convive_sub)
order_operation_5 = CreationOrderOperationFactory(order=order_5)
with patch('orders.models.Order.subvention_triggered_same_day', return_value=True):
with patch('builtins.hasattr', return_value=False):
self.assertIsNone(order_operation_5._get_subvention())
I read stuff about this error on stack overflow, and concluded that I should avoid mocking the same stuff (stacked mocks). But it's not what I'm doing here. I'm nesting mocks, and it seems to be ok.
If I invert the return values (first mock returns False, second returns True), the test works well.
Any idea?
Thanks.

In short, you cannot patch the builtins func hasattr
patch('builtins.hasattr', return_value=False)
reason: used by mock.py
if not _is_started(self):
raise RuntimeError('stop called on unstarted patcher')
def _is_started(patcher):
# XXXX horrible
return hasattr(patcher, 'is_local')
to repeat the error:
#mock.patch('__builtin__.hasattr')
def test_mock_hasattr(self, mocked_hasattr):
# as long as it is set to False, it will trigger
mocked_hasattr.return_value = False
to mock a builtins func inside models.py:
# narrow the mock scope
#mock.patch('orders.models.hasattr')

Related

Python mock testing: How do I alter an Object in a mocked method?

I'm trying to test this kind of method that alters my incoming object, but it needs to be mocked because I can't run the code in a unit test case (requires massive files that aren't in the repo). I want to mock method_to_mock so that it sets the file_wrapper_object.status to whatever I want for that test case, notice the method doesn't return anything.
def method_to_mock(file_wrapper_object):
try:
process(file_wrapper_object) #<—- I can't run this in a test case
file_wrapper_object.status = "COMPLETE"
except:
file_wrapper_object.status = "FAILED"
def TestProcessFileWrapperObject(self):
file_wrapper_object = create_wrapper_object(arbitrary_data)
method_to_mock(file_wrapper_object)
self.assertEqual(file_wrapper_object.status, "COMPLETE")
How can I mock a method that doesn't return anything but alters the incoming object?
You can use the side_effect to specify a custom behavior for the mock object. Here is an example:
import unittest
from unittest.mock import MagicMock
def TestProcessFileWrapperObject(self):
file_wrapper_object = create_wrapper_object(arbitrary_data)
my_method_mock = MagicMock(return_value=None)
def side_effect(file_wrapper_object):
file_wrapper_object.status = "COMPLETE"
my_method_mock.side_effect = side_effect
my_method = my_method_mock
my_method(file_wrapper_object)
self.assertEqual(file_wrapper_object.status, "COMPLETE")
Please check this for more details.

How to unittest two function return values in a function using python unitttest

I have a function in a python program which does a function call twice:
def add_user(uname,dserver,pwd,dinstance,proc1, query1):
db_conn = db_connect(uname,dserver,pwd,dinstance)
if db_conn is not_conn:
print("Failed to connect")
sys.exit(-1)
db_conn.run_proc(proc1)
if db_conn.error_msg:
print("Failed procedure")
sys.exit(-1)
db_conn.run_query(query1)
if db_conn.err_msg:
print("Failed query")
sys.exit(-1)
Now the unit test is as follows:
#patch('mydir.proj_dir.db_execu.db_connect')
def test_add_user(self, mock_conn):
mock_conn.return_value.not_conn.return_value = True
mock_conn.return_value.run_proc.return_value = True
mock_conn.return_value.run_query.return_value = True
mock_conn.return_value.err_msg.side_effect= [True, False]
with self.assertRaises(SystemExit):
add_user(name,dserver,pwd,dinstance,proc1, query1)
print("failed query")
My objective is to test the second error condition. But after adding side_effect it only going to first condition which is displaying as "Failed procedure". I want to test "Failed Query" condition. First two error conditions I have tested but the third one is failing and calling the second condition always. Please advise.
It's not super clear what you're asking exactly. You're calling add_user only once so you're only testing a single "path", you need to call it multiple times to test multiple "paths" through it.
That aside, there's a huge issue with your understanding of mock return_value and side_effect configure mocks as callables, when accessing attributes but not calling them, you're just getting the next mock in the chain, you're not using any of your configuration. mock also supports configuring some of the "magic methods" of the data model, but it does not support configuring __getattr__ (the "simple" attribute access) as that's used internally by mock.
As a result, you should only use mock to patch / replace db_connect to return a pseudo-connection, but rather than a mock that pseudo-connection should be a fake: an object you build yourself which looks like a connection but behaves however you want it e.g.
class PseudoConnection:
def __init__(self, fail_proc=False, fail_query=False):
self._fail_proc = fail_proc
self._fail_query = fail_query
self.err_msg = None # or error_msg, or both
def run_proc(self, proc):
self.err_msg = self._fail_proc
def run_query(self, query):
self.err_msg = self._fail_query
Then you just configure mock_conn.return_value = PseudoConnection(True, False) to test the first case and mock_conn.return_value = PseudoConnection(False, True) for the second.
Also as hinted by the comment you're accessing both error_msg and err_msg, and only attempting to configure err_msg. I don't know which is right, but I doubt both are.

How to test a function that uses a decorator cache.memoize

I'am having problems testing a function that has a decorator for caching:
#retry(stop=stop_after_attempt(3))
#cache.memoize(60)
def get_azure_machine_info(rg_name, machine_name, expand="instanceView"):
try:
compute_client = get_azure_compute_client()
return compute_client.virtual_machines.get(rg_name, machine_name, expand=expand)
except CloudError:
return None
My test:
#patch("dev_maintenance.machines.get_azure_compute_client")
def test_get_azure_machine_info(get_azure_compute_client):
cache.delete_memoized('get_azure_machine_info')
with app.app_context():
ret = get_azure_machine_info("rg1", "m1")
get_azure_compute_client.assert_called_once()
assert len(get_azure_compute_client.return_value.method_calls) == 1
assert (
ret == get_azure_compute_client.return_value.virtual_machines.get.return_value
)
get_azure_compute_client.return_value.virtual_machines.get.assert_called_once_with(
"rg1", "m1", expand="instanceView"
)
Before i used cache the test was working fine, but now i can't figure out what is going on here.
The error:
The cache is trying to do its thing with the MagicMock objects that patch is creating. And it's failing since it 'can't pickle class unitest.mock.MagicMock'
The simplest work-around would be to mock out the cache module in the test. You can look here for pointers: Can I patch a Python decorator before it wraps a function?
Do this in a setUp() fixture.

Mocked unit test raises a "stop called on unstarted patcher" error

When running the test bellow, I got a stop called on unstarted patcher.
def test_get_subvention_internal_no_triggered_admission(self):
billing_cluster = BillingClusterFactory()
subvention = SubventionFactory(billing_cluster=billing_cluster)
convive_sub = ConviveFactory(subvention=subvention, billing_cluster=billing_cluster)
order_5 = OrderFactory(beneficiary=convive_sub)
order_operation_5 = CreationOrderOperationFactory(order=order_5)
with patch('orders.models.Order.subvention_triggered_same_day', return_value=True):
with patch('builtins.hasattr', return_value=False):
self.assertIsNone(order_operation_5._get_subvention())
I read stuff about this error on stack overflow, and concluded that I should avoid mocking the same stuff (stacked mocks). But it's not what I'm doing here. I'm nesting mocks, and it seems to be ok.
If I invert the return values (first mock returns False, second returns True), the test works well.
Any idea?
Thanks.
In short, you cannot patch the builtins func hasattr
patch('builtins.hasattr', return_value=False)
reason: used by mock.py
if not _is_started(self):
raise RuntimeError('stop called on unstarted patcher')
def _is_started(patcher):
# XXXX horrible
return hasattr(patcher, 'is_local')
to repeat the error:
#mock.patch('__builtin__.hasattr')
def test_mock_hasattr(self, mocked_hasattr):
# as long as it is set to False, it will trigger
mocked_hasattr.return_value = False
to mock a builtins func inside models.py:
# narrow the mock scope
#mock.patch('orders.models.hasattr')

AttributeError: while using monkeypatch of pytest

src/mainDir/mainFile.py
contents of mainFile.py
import src.tempDir.tempFile as temp
data = 'someData'
def foo(self):
ans = temp.boo(data)
return ans
src/tempDir/tempFile.py
def boo(data):
ans = data
return ans
Now I want to test foo() from src/tests/test_mainFile.py and I want to mock temp.boo(data) method in foo() method
import src.mainDir.mainFile as mainFunc
testData = 'testData'
def test_foo(monkeypatch):
monkeypatch.setattr('src.tempDir.tempFile', 'boo', testData)
ans = mainFunc.foo()
assert ans == testData
but I get error
AttributeError: 'src.tempDir.tempFile' has no attribute 'boo'
I expect ans = testData.
I would like to know if I am correctly mocking my tempDir.boo() method or I should use pytest's mocker instead of monkeypatch.
You're telling monkeypatch to patch the attribute boo of the string object you pass in.
You'll either need to pass in a module like monkeypatch.setattr(tempFile, 'boo', testData), or pass the attribute as a string too (using the two-argument form), like monkeypatch.setattr('src.tempDir.tempFile.boo', testData).
My use case was was slightly different but should still apply. I wanted to patch the value of sys.frozen which is set when running an application bundled by something like Pyinstaller. Otherwise, the attribute does not exist. Looking through the pytest docs, the raising kwarg controls wether or not AttributeError is raised when the attribute does not already exist. (docs)
Usage Example
import sys
def test_frozen_func(monkeypatch):
monkeypatch.setattr(sys, 'frozen', True, raising=False)
# can use ('fq_import_path.sys.frozen', ...)
# if what you are trying to patch is imported in another file
assert sys.frozen
Update: mocking function calls can be done with monkeypatch.setattr('package.main.slow_fun', lambda: False) (see answer and comments in https://stackoverflow.com/a/44666743/3219667) and updated snippet below
I don't think this can be done with pytest's monkeypatch, but you can use the pytest-mock package. Docs: https://github.com/pytest-dev/pytest-mock
Quick example with the two files below:
# package/main.py
def slow_fun():
return True
def main_fun():
if slow_fun():
raise RuntimeError('Slow func returned True')
# tests/test_main.py
from package.main import main_fun
# Make sure to install pytest-mock so that the mocker argument is available
def test_main_fun(mocker):
mocker.patch('package.main.slow_fun', lambda: False)
main_fun()
# UPDATE: Alternative with monkeypatch
def test_main_fun_monkeypatch(monkeypatch):
monkeypatch.setattr('package.main.slow_fun', lambda: False)
main_fun()
Note: this also works if the functions are in different files

Categories

Resources