mock patch multiple user inputs in sequence - python

similar questions have been asked many times, but I can't seem to figure out this simple test I am trying to build: I would like to first supply a "y", and then a "n" to a complex function requiring user input (i.e. it requires two inputs in sequence). This is my attempt - the with statement doesn't advance the iterator, but I don't know how I would implement patched input otherwise.
import mock
m = mock.Mock()
m.side_effect = ["y","n"]
#pytest.fixture(scope="module")
def test_my_complex_function():
with mock.patch('builtins.input', return_value=m()):
out = my_complex_function(some_args)
return out

If I understood well the problem, you have a fucntion that have a similar behavior like this.
module.py
def complex_function():
first = input("First input")
second = input("Second input")
return first, second
And you would to like to mock the input builtin method. You were in the right way, the only point to fix is that you have to build 2 mocks. One for each input instance.
test_module.py
import pytest
from unittest.mock import Mock, patch
from module import complex_function
input_mock_y = Mock() # First mock for first input call
input_mock_n = Mock() # Second mock for second input call
input_mock = Mock() # Combine the 2 mocks in another mock to patch the input call.
input_mock.side_effect = [input_mock_y.return_value, input_mock_n.return_value]
def test_my_complex_function():
with patch('builtins.input', input_mock) as mock_input:
result = complex_function()
assert mock_method.call_count == 2
You may say: Ok, but how do I know that each input was patched correctly?
So, you can especify some return value to any input mock, so you can compare.
input_mock_y = Mock()
input_mock_y.return_value = "Y"
input_mock_n = Mock()
input_mock_n.return_value = "N"
input_mock = Mock()
input_mock.side_effect = [input_mock_y.return_value, input_mock_n.return_value]
def test_my_complex_function():
with patch('builtins.input', input_mock) as mock_method:
result = function()
assert mock_method.call_count == 2
assert result == ('Y', 'N')

Related

Test assert function order with mocks (pytest -> assert_has_calls)

I'm trying to test the order of the sub-functions inside of the main function:
def get_data():
pass
def process_data(data):
pass
def notify_admin(action):
pass
def save_data(data):
pass
def main_func():
notify_admin('start')
data = get_data()
processed_data = process_data(data)
save_data(processed_data)
notify_admin('finish')
I'm using pytest, so far I've come up with this:
import pytest
from unittest.mock import patch, Mock, call
from main_func import main_func
#patch('main_func.notify_admin')
#patch('main_func.get_data')
#patch('main_func.process_data')
#patch('main_func.save_data')
def test_main_func(mock_4, mock_3, mock_2, mock_1):
execution_order = [mock_1, mock_2, mock_3, mock_4]
order_mock = Mock()
for order, mock in enumerate(execution_order):
order_mock.attach_mock(mock, f'f_{order}')
main_func()
order_mock.assert_has_calls([
call.f_1(),
call.f_2(),
call.f_3(),
call.f_4(),
call.f_1(),
])
This is an error, which I'm not sure how to resolve:
E AssertionError: Calls not found.
E Expected: [call.f_1(), call.f_2(), call.f_3(), call.f_4(), call.f_1()]
E Actual: [call.f_1('start'),
E call.f_2(),
E call.f_3(<MagicMock name='mock.f_3()' id='2049968460848'>),
E call.f_4(<MagicMock name='mock.f_2()' id='2049968489424'>),
E call.f_1('finish')]
Could you please suggest ways to resolve it or maybe implement it in a different way?
I've read documentation of assert_has_calls but I'm still not sure how to use it for this particular case.
If you want to check the call order without the argument list, you can use the method_calls attribute of the mock, which contains a list of calls in the order they are made, and only check their name:
...
main_func()
assert len(order_mock.method_calls) == 4
assert order_mock.method_calls[0][0] == "f_1"
assert order_mock.method_calls[1][0] == "f_2"
assert order_mock.method_calls[2][0] == "f_3"
assert order_mock.method_calls[3][0] == "f_4"
Each method call is a tuple of name, positional arguments and keyword arguments, so if you want to check only the name you can just use the first index.
Note that the output of your test does not seem to match this, but this is a matter of your actual application logic.
If you are using has_calls, you have to provide each argument, which is also possible. This time taking the actual result of your test, something like this should work:
...
main_func()
order_mock.assert_has_calls([
call.f_1('start'),
call.f_2(),
call.f_3(mock1),
call.f_4(mock2),
call.f_1('finish')
])

Mock testing with nested function changes

I am adding testing to a pipeline project, code is already written and in production so it cannot be changed to accommodate the tests.
In simplest terms, if I have a function like so:
def other_foo():
return 1
def foo():
res = other_foo()
return res
In practicality, the other_foo call will return a variety of responses, but for testing, I want to create a fixed response to test foo.
So in my test I want to create a fixed response to other_foo of 2. and my test evaluation to be something like:
def test_foo():
# some mocking or nesting handle here for other_foo
res = foo()
assert res == 2
Use the patch decorator from unitest.mock and patch your module local variable.
from your.module import foo
from unitest.mock import patch
#patch('your.module.other_foo')
def test_foo(mock_other_foo):
mock_other_foo.return_value = 3
assert foo() == 3
mock_other_foo.return_value = 42
assert foo() == 42
You can find more information here and there.

Mock nested method in Python

Wracking my brain on this. I want to mock generator methods self.api.redditor(username).comments.new(limit=num) and self.api.redditor(username).submissions.new(limit=num) below, in which self.api is assigned to a class instance, as in self.api = PrawReddit()
I'm trying to test the size of the result: self.assertEqual(len(result), 5)
So far, I tried MockPraw.return_value.redditor.return_value.comments.return_value.new.return_value.__iter__.return_value = iter(['c' * 10]) but the test fails with AssertionError: 0 != 5
Any tips much appreciated.
def get_comments_submissions(self, username, num=5):
"""Return max `num` of comments and submissions by `username`."""
coms = [
dict(
title=comment.link_title,
text=comment.body_html,
subreddit=comment.subreddit_name_prefixed,
url=comment.link_url,
created=datetime.fromtimestamp(comment.created_utc, pytz.utc),
)
for comment in self.api.redditor(username).comments.new(limit=num)
]
subs = [
dict(
title=submission.title,
text=submission.selftext_html,
subreddit=submission.subreddit_name_prefixed,
url=submission.url,
created=datetime.fromtimestamp(submission.created_utc, pytz.utc),
)
for submission in self.api.redditor(username).submissions.new(limit=num)
]
return coms + subs if len(coms + subs) < num else (coms + subs)[:num]
To mock a generator (unless you are using specific generator features) you can use an iterator as a stand-in eg
import unittest.mock as mock
generator_mock = Mock(return_value=iter(("foo", "bar")))
When you have nested structures like in your example this gets a little more complex, attribute access is automatically handled but return_value from a function must be defined. From your example:
# API mock
mock_api = Mock()
mock_api.redditor.return_value = mock_subs = Mock()
# Submissions mock
mock_subs.new.return_value = iter(("foo", "bar"))
This can then be called and asserted
for item in mock_api.api.redditor("user").submissions.new(limit=5):
print(item)
mock_api.redditor.assert_called_with("user")
mock_subs.new.assert_called_with(limit=5)
As the API is a member of the same class, this is going to have to be monkey patched eg:
target = Praw()
target.api = mock_api()
target.get_comments_submissions("user")
mock_api.redditor.assert_called_with("user")
mock_subs.new.assert_called_with(limit=5)
Note that the iterator in return value is a single instance and a second call to get the iterator will return the same instance.
Writting like you use pytest-mock and everything happens in mymodule (you imported the class at the top of the module like from xy import PrawReddit):
mocker.patch("datetime.fromtimestamp")
mocked_comment = mocker.MagicMock()
mocked_submission = mocker.MagicMock()
mocked = mocker.patch("mymodule.PrawReddit")
mocked.return_value.redditor.return_value.comments.new.return_value = [mocker.MagicMock(), mocked_comment]
mocked.return_value.redditor.return_value.submisions.new.return_value = [mocker.MagicMock(), mocked_submission]
returned = instance.get_comments_submissions("foo", num=2)
assert mocked.return_value.redditor.call_count = 2
mocked.return_value.assert_called_with("foo")
assert returned[-1]["link_title"] == mocked_comment.link_title
Another test call with the same intro:
# ...
returned = instance.get_comments_submissions("foo")
assert mocked.return_value.redditor.call_count = 2
mocked.return_value.assert_called_with("foo")
assert returned[1]["link_title"] == mocked_comment.link_title
assert returned[-1]["title"] == mocked_submission.title

How to mock a function with specific parameter and return value that calls another function in python unit test?

i am new to python unit testing. Want to mock a function that calls other functions.
Here is my function that i want to mock
def has_groups(self, group_names):
auth_user_id = AuthUser.get_by_email(self.userEmail).id
auth_user_groups = AuthUserGroups.get_group_by_user_id(auth_user_id)
for auth_user_group in auth_user_groups:
if auth_user_group.group.name in group_names:
return True
return False
has_groups should return True only when it get's 'Admin' as parameter.
Here is my test
def my_test(self):
uid = self.auth_user.get_by_email = Mock(return_value=73)
groups = AuthUserGroups.get_group_by_user_id = Mock(uid, return_value='Admin')
self.auth_user.has_groups = Mock(groups, return_value=True)
but it's not working fine. I will appreciate if anyone help me
Can i use patch decorator for this and how?
As I understand your has_groups function is method. I think it's better to mock whole class or independent function. On this situation you could mock AuthUserGroups, method return value and patch module with has_groups method implementation. So you'll have test like this:
from unittest import mock
def my_test(self):
group = mock.MagicMock()
group.group.name = 'Admin'
fake_auth_user_groups = mock.MagicMock()
fake_auth_user_groups.get_group_by_user_id.return_value = [group]
with mock.patch('your_module.AuthUserGroups', fake_auth_user_groups):
self.auth_user.has_groups(['Admin'])

Python Mock function return always False

I try to add unit test in python in function that save stats in a file
Here is the function for saving
def save_file_if_necessary(file_path, content, current_time, mode="w", delta_time=60, force=False):
if file_path not in file_save or current_time - file_save[file_path] >= delta_time or force:
with codecs.open(file_path, mode, encoding="utf-8") as written_file:
written_file.write(content)
file_save[file_path] = time.time()
print "yes"
return True
else:
print "not necessary"
return False
I make a call of this function like that
def test_function():
bot_url_dic = {"seven1": 10,
"seven2": 20
}
save_file_if_necessary(os.path.join("./", "recipients.bots"),json.dumps(bot_url_dic, ensure_ascii=False, indent=4), time.time())
And i made some unittest with mock to test if the function is called
from test import save_file_if_necessary, test_function
def test_call_save_file_if_necessary(self):
"""test function to test add in list."""
ip_dic = ["seven1", "seven2", "seven3"]
save_file_if_necessary = Mock()
test_function()
self.assertTrue(save_file_if_necessary.called)
But the problem is Mock is always return False but the function is called at least one time.
self.assertTrue(save_file_if_necessary.called)
AssertionError: False is not true
(python version 2.7.6)
All you've done is create a new Mock object, coincidentally called "save_file_if_necessary". You haven't done anything to replace the actual function with your mock.
You need to use the patch functionality to actually do that:
#mock.patch('my_test_module.save_file_if_necessary')
def test_call_save_file_if_necessary(self, mock_function):
ip_dic = ["seven1", "seven2", "seven3"]
test_function()
self.assertTrue(mock_file.called)
You need to import the module where the function is defined and assign a Mock to your function:
import test
def test_call_save_file_if_necessary(self):
"""test function to test add in list."""
ip_dic = ["seven1", "seven2", "seven3"]
test.save_file_if_necessary = Mock()
test.test_function()
self.assertTrue(test.save_file_if_necessary.called)
Or, use the patching function instead.

Categories

Resources