Mock nested method in Python - 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

Related

mock queryset in Django unittest

I have sample code and test:
def outer():
inner_response = inner(param1)
def inner(something):
queryset_response = something.object.filter(foo="bar",
foo1="bar1") #this should get a reponse when testing.
response_list = []
for count, queryset_res in enumerate(queryset_response):
response_list.append(queryset_response[count].data)
#get response for data in this line.
return response_list
I wanna test this situation using mock and probably return list of queryset using mock if possible.
def setup():
something = mock.Mock()
def test_outer():
# what should be done to the below line work so that
# response_list.append gets some value.
something.objects.filter()[0].data = "some string"
# Also is it possible to return queryset as like shown below.
something.objects.filter().return_value = <queryset> # list of objects in queryset.
i used a mock and returned namedtuple to make it behave like queryset and use (.) dot to access the data set. I could make a class and used it same way.
def test_func():
something = mock.Mock()
key = collections.namedtuple('key', 'data')
response = key('some_string')
something.objects.filter.return_value = [response]
this is kinda mocking django as gloo said, my ex-engineers decided to opt in that way.
You shouldn't be mocking the result of a filter as that would be like unit testing django itself. Instead, you should be testing your own functions that will call Model.object.filter. You can create the objects you will be working with in the setup of your unit test, and assert that when you call your function, the expected result is the same as those objects. For example:
def my_own_function(foo, foo1):
queryset_response = Something.objects.filter(foo=foo, foo1=foo1)
store = queryset_response[0].data
return store
and in your unit test:
def test_my_own_function():
data = "mydata"
sample_obj = Something.objects.create(foo="bar", foo1="bar1", data=data)
result = my_own_function("bar", "bar1")
self.assertEqual(result, data)

how mock requests.get.return_value with different values in python

I am writing a test case with pytest to test a rest api call to get a token. The token will be expired in 15 minutes. I am testing the api will be called twice with different tokens when expired. Following is my test method:
#patch("package.api.requests.get")
def test_get_access_token_expired(mocked_requests_get):
mocked_requests_get.return_value.text = "".join(
secrets.choice(string.ascii_letters + string.digits) for i in range(7)
)
client = MyClient("", "", "", "")
client._get_access_token()
token1 = client._access_token
client._token_expired_time = datetime.now() - timedelta(minutes=15)
client._get_access_token()
token2 = client._access_token
print(f"token1: {token1}, token2: {token2}")
assert token1 != token2
assert mocked_requests_get.call_count == 2
The mocked requests did get called twice. But somehow the token1 and token2 are always the same. Not sure why? Can anyone tell me what I missed? Also, by the document, the target of the patch should be package.module.ClassName, but if I put my class name there like package.api.MyClient.requests.get, it gave me ModuleNotFoundError. Why?
Thanks for the response. I tried with side_effect like:
mocked_requests_get.side_effect=[
Mock(**{"return_value": {"text": "abc"}}), Mock(**{"return_value": {"text": "123"}})
]
The token1 is different than token2. But how come the token1 is assigned like <Mock name='mock.text' id='4485261536'>? How could I make the token1 is "abc"?
return_value will always return the same fixed value.
Set this to configure the value returned by calling the mock:
If you want a function to be called you should look at side_effect:
This can either be a function to be called when the mock is called, an iterable or an exception (class or instance) to be raised
Option 1: Changing the return_value
Just set the return value twice
from mock import patch
import requests
if __name__ == "__main__":
with patch("requests.get") as mocked_requests_get:
mocked_requests_get.return_value.text = 1
print(requests.get("nice").text)
mocked_requests_get.return_value.text = 2
print(requests.get("nice").text)
# output
# >> 1
# >> 2
Option 2: Using side_effect
side_effect is a bit trickier, in that you can't immediately specify an attribute to apply to (at least to my knowledge).
It may be easier/clearer to return an iterable of mock response objects like:
from mock import patch, MagicMock
from random import randint
import requests
def some_random_key(*args, **kwargs):
return "".join([str(randint(0, 9)) for _ in range(7)])
class MockRequestResponse(object):
def __init__(self, text):
self.text = text
if __name__ == "__main__":
with patch("requests.get") as mocked_requests_get:
mocked_requests_get.side_effect = [
MockRequestResponse(text=some_random_key()) for _ in range(2)
]
print(requests.get("nice").text)
print(requests.get("nice").text)
# output
# >> 9604410
# >> 2126280
If you do find yourself using needing more of the features/attributes of the Response object, rather than making your own mock response object, you may want to consider requests-mock's Response Lists as suggested in the comments.
Where to patch
My general guidance would be to patch what you can import from another file. e.g. you cannot patch package.api.MyClient.requests.get as you wouldn't be able to import requests from your MyClient class. The package.module.ClassName in documentations is therefore allowed as you can import a class from another file.

mock patch multiple user inputs in sequence

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

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

How to stub method in mocked object?

I need to test code snippet (e.g. from class UnderTestClass):
def _method_to_test(self)
...
ParsingObject = MyParsingClass()
if not ParsingObject.parse_string(some_string):
self.logger.error('Parsing data has failed.')
return False
return ParsingObject
No matter how I try, can't cover last return statement - return ParsingObject, so it have to be something wrong with my mocking of parse_string() method.
I've tried inter alia statements from Python testing tutorial:
from my_app import myParsingClass
...
def test_method_to_test_success(self):
...
UnderTestClassMock = Mock(name='UnderTestClass')
parsePatch = patch('my_app.myParsingClass.MyParsingClass')
parseMock = parsePatch.start()
parseInstance = parseMock.return_value
parseInstance.parse_string.return_value = True
res = tested_module.UnderTestClass._method_to_test(UnderTestClassMock)
parsePatch.stop()
self.assertIsInstance(res, myParsingClass.MyParsingClass)
But unfortunately get only:
AssertionError: False is not an instance of class 'my_app.myParsingClass.MyParsingClass'
UPDATE: Thanks. I follow your advice, so re-write a bit:
with patch('...') as ParseMock:
instance = ParseMock.return_value
ParseMock.parse_string.return_value = True
res = tested_module.UnderTestClass._method_to_test(UnderTestClassMock)
assert myParsingClass.MyParsingClass() is instance
assert myParsingClass.MyParsingClass() is res
but still got AssertionError on last line.
EDIT: do I need somehow dependency injection mechanism/framework?
You need to set return_value on parseMock, not parseInstance:
parseMock.parse_string.return_value = True
Also you need to stop() the patch after your assert
You should mock the instance method parse_string, not the class method.
In [22]: import mock
In [23]: ParseMock = mock.Mock()
In [24]: instance = ParseMock.return_value
In [25]: instance.parse_string.return_value = True
In [26]: parser = ParseMock()
In [27]: parser.parse_string("foo")
Out[27]: True

Categories

Resources