Mocked unit test raises a "stop called on unstarted patcher" error - 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

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

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')

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 mock PythonOperator's python_callable and on_failure_callback?

What and where do I mock, with an Airflow PythonOperator, such that:
the python_callback raises a exception, triggering the call of the on_failure_callback, and
I can test whether that callback is called, and with what arguments?
I have tried mocking both the {python_callable} and PythonOperator.execute, in a number of places, without success.
The code files look something like this:
dags/my_code.py
class CustomException(Exception): pass
def a_callable():
if OurSqlAlchemyTable.count() == 0:
raise CustomException("{} is empty".format(OurSqlAlchemyTable.name))
return True
def a_failure_callable(context):
SlackWebhookHook(
http_conn_id=slack_conn_id,
message= context['exception'].msg,
channel='#alert-channel'
).execute()
dags/a_dag.py
from my_code import a_callable, a_failure_callable
new_task = PythonOperator(
task_id='new-task', dag=dag-named-sue, conn_id='a_conn_id', timeout=30,
python_callable=a_callable,
on_failure_callback=a_failure_callable)
dags/test_a_dag.py
class TestCallback(unittest.TestCase):
def test_on_failure_callback(self):
tested_task = DagBag().get_dag('dag-named-sue').get_task('new-task')
with patch('airflow.operators.python_operator.PythonOperator.execute') as mock_execute:
with patch('dags.a_dag.a_failure_callable') as mock_callback:
mock_execute.side_effect = CustomException
tested_task.execute(context={})
# does failure of the python_callable trigger the failure callback?
mock_callback.assert_called()
# did the exception message make it to the failure callback?
failure_context = mock_callback.call_args[0]
self.assertEqual(failure_context['exception'].msg,
'OurSqlAlchemyTable is empty')O
The test does raise CustomException at the line self.task.execute(context={}) -- but, in the test code itself. What I want is for that error to be
raised in the Airflow code such that the PythonOperator fails and calls on_failure_callback.
I have tried any number of permutations, all either raising in test without
triggering, calling the python_callable, or not finding an object to patch:
patch('dags.a_dag.a_callable') as mock_callable
'a_dag.a_callable'
'dags.my_code.a_callable'
'my_code.a_callable'
'airflow.models.Task.execute'
(Python3, pytest, and mock.)
What am I missing / doing wrong?
(Better still, I would like to verify the arguments passed to SlackWebhookHook. Something like:
with patch('???.SlackWebhookHook.execute') as mock_webhook:
... as above ...
kw_dict = mock_webhook.call_args[-1]
assert kw_dict['http_conn_id'] == slack_conn_id
assert kw_dict['message'] == 'OurSqlAlchemyTable is empty'
assert kw_dict['channel'] == '#alert-channel'
(But I am first focussing on testing the failure callback.)
Thank you in advance.

Python mock failing call assertion

I have been reading into python mocking but can't get my head around why the following code is failing.
I have two classes, a Potato and a PotatoBag like the following. Figure is stored in food.py and Report is stored in bag.py.
class Potato:
def create_potato(self):
pass
def output_potato(self):
pass
class PotatoBag:
def __init__(self, potatoes):
self.potatoes = potatoes
def output_to_file(self):
for fig in self.potatoes:
fig.create_potato()
fig.output_potato()
Currently I am trying to unit test the output method so that Report correctly calls create_figure and output_figure from Figure using a mock. This is my test code:
from unittest.mock import MagicMock, patch
from bag import PotatoBag
from food import Potato
import pytest
#pytest.fixture(scope='module')
def potatoes():
x = Potato()
y = Potato()
return [x, y]
#patch('food.Potato')
def test_output_to_file(mock_potato, potatoes):
test_potato_bag = PotatoBag(potatoes)
test_potato_bag.output_to_file()
mock_potato.return_value.create_potato.assert_called()
mock_potato.return_value.output_potato.assert_called()
Immediately pytest yields an AssertionError stating that create_figure was never called.
_mock_self = <MagicMock name='Potato().create_potato' id='140480853451272'>
def assert_called(_mock_self):
"""assert that the mock was called at least once
"""
self = _mock_self
if self.call_count == 0:
msg = ("Expected '%s' to have been called." %
self._mock_name or 'mock')
> raise AssertionError(msg)
E AssertionError: Expected 'create_potato' to have been called.
/home/anaconda3/lib/python3.7/unittest/mock.py:792: AssertionError
What is wrong with my code?
You are passing the Report a list of Figures from your fixture instead of a mock.
Changing your test to...
#patch('figure.Figure')
def test_output_to_file(mock_figure, figures):
test_report = Report([mock_figure])
test_report.output_to_file()
mock_figure.create_figure.assert_called_once()
mock_figure.output_figure.assert_called_once()
This resolves testing that output_to_file correctly is calling the functions on Figure without actually worrying about setting up a figure and dealing with any side effects or additional complexities that may come with calling those functions. The worries of that can be saved for the unit tests for Figure ;)

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