1

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]
2
  • What have you tried with mock? Commented Aug 6, 2019 at 1:11
  • something like this: MockPraw.return_value.redditor.return_value.comments.return_value.new.return_value.__iter__.return_value = iter(['c' * 10]) Commented Aug 6, 2019 at 1:14

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

This helped. Thanks, Tim!
1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.