To test the following functino, I want to assert .filter() is called once with parameter filters.
def get_instances(session: boto3.session.Session, filters):
instances = session.resource('ec2').instances.filter(Filters=filters)
return instances
I tried to write the unit test,
def test_get_instance():
mocked_session: boto3.session.Session = boto3.session.Session()
def resource(service_name) -> boto3.resources.base.ServiceResource:
if service_name == 'ec2':
return MagicMock()
raise Exception('Parameter should be ec2')
mocked_session.resource = MagicMock(side_effect=resource) # resource is set?
mocked_session.resource('ec2').instances = MagicMock()
# mocked_session.resource('ec2').instances. ??? # not finished
filters = None
Node.get_nodes(mocked_session, filters)
assert session.resource('ec2').instances.filter.call_count == 1
However, the test function got the following error
> assert session.resource('ec2').instances.filter.call_count == 1
E AttributeError: 'function' object has no attribute 'resource'
You can use the built-in monckeypatch fixture for that purpose.
def test_get_instance(monkeypatch):
# ... your code
filter_calls = []
monkeypatch.setattr(mocked_session.resource('ec2'), 'filter', lambda Filters: filter_calls.append(1))
assert len(filter_calls) == 1
I did not test it with the boto3 lib, however, the idea is here.
Related
The following code got error of "TypeError: 'ec2.instancesCollectionManager' object is not iterable" when casting to list(instances) using Moto.
#pytest.fixture(scope='function')
def aws_credentials():
os.environ['AWS_ACCESS_KEY_ID'] = 'testing'
os.environ['AWS_SECRET_ACCESS_KEY'] = 'testing'
os.environ['AWS_SECURITY_TOKEN'] = 'testing'
os.environ['AWS_SESSION_TOKEN'] = 'testing'
os.environ["MOTO_ALLOW_NONEXISTENT_REGION"] = 'True'
os.environ['AWS_DEFAULT_REGION'] = 'us-east-1'
#pytest.fixture
def session(aws_credentials):
return boto3.session.Session()
#mock_ec2
def test_get_nodes(session):
#session = boto3.session.Session(region_name='us-east-1')
filters = None
Node.get_nodes(session, filters)
instances = session.resource('ec2').instances
print(list(instances)) # ERR
assert any(instances) # ERR
The instance in Node class can be casted to list() if not using Moto.
class Node(NodeBase):
#classmethod
def get_nodes(cls, session, filters):
instances = session.resource('ec2').instances.filter(Filters=filters)
return instances
print(type(instances)) got <class 'boto3.resources.collection.ec2.instancesCollection'> whenever using Moto or not.
In node, you are calling instances.filter(), which returns a <class 'boto3.resources.collection.ec2.instancesCollection'>. This collection is iterable and hence can be unpacked into. a list.
However, in your test, you are not calling filter on the .instance. Here, the type of instances is
<class 'boto3.resources.collection.ec2.instancesCollectionManager'> which is not iterable. If you call .all() of .filer() on that, you get back a collection again which is iterable.
I'm trying to mock a class method with pytest-mock. I have the code below in a single file, and when the test is run I get ModuleNotFoundError: No module named 'RealClass' in the patch function. How to make this work?
class RealClass:
def some_function():
return 'real'
def function_to_test():
x = RealClass()
return x.some_function()
def test_the_function(mocker):
mock_function = mocker.patch('RealClass.some_function')
mock_function.return_value = 'mocked'
ret = function_to_test()
assert ret == 'mocked'
In your case since you are patching the class that is present within the test file itself you would use mocker.patch.object.
mock_function = mocker.patch.object(RealClass, 'some_function')
collected 1 item
tests/test_grab.py::test_the_function PASSED [100%]
============================== 1 passed in 0.03s ===============================
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
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'])
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