combine httpretty with pytest tmpdir - python

The following pytest-test uses httpretty, to mock a request. It writes the fetched data to a file:
import requests
import httpretty
import json
from os import listdir
from os.path import join
#httpretty.activate
def test_write_file_from_datasource():
tmpdir = './mytestdir'
# mock the connection
concert_url = 'http://apis.is/concerts'
httpretty.register_uri(httpretty.GET, concert_url,
body = json.dumps({'results': []}),
content_type='application/json')
# fetch data
concerts = requests.get(concert_url).json()
# write data
with open(join(tmpdir, 'concerts.json'), 'w') as json_data:
json.dump(concerts, json_data, indent=2)
assert len(listdir(tmpdir)) == 1
What I would like to do now, is making use of the pytest tmpdir feature. To reach this, I wrote a test like this (imports same as above):
#httpretty.activate
def test_write_file_from_datasource_failing(tmpdir):
tmpdir = str(tmpdir)
# mock the connection
concert_url = 'http://apis.is/concerts'
httpretty.register_uri(httpretty.GET, concert_url,
body = json.dumps({'results': []}),
content_type='application/json')
# fetch data
concerts = requests.get(concert_url).json()
# write data
with open(join(tmpdir, 'concerts.json'), 'w') as json_data:
json.dump(concerts, json_data, indent=2)
assert len(listdir(tmpdir)) == 1
It fails, because the httpretty decorator seems to have problems with the additional parameter:
TypeError: test_write_file_from_datasource_failing() takes exactly 1 argument (0 given)
Any ideas, how to fix this?

It seems that this decorator does not work well with pytest's funcargs.
The only solution I see is to manually call httprertty.enable() and httpretty.disable() methods.
Or create a fixture:
#pytest.yield_fixture
def http_pretty_mock():
httpretty.enable()
yield
httpretty.disable()
def test_write_file_from_datasource_failing(http_pretty_mock, tmpdir):
tmpdir = str(tmpdir)
# mock the connection

Related

Python test patch is never called

I am trying to test with my code by mocking the PyGithub library.
I want to create a repository for an organization. So first I need to get it and on the "Organization" returned object, I need to make another call.
It fails when trying to assert that my second method was called.
I am very new to python and I am guessing that there is a missing connection between the mocks but I cannot figure out what.
class GithubService:
def __init__(self, token: str) -> None:
self.__github__ = Github(token)
self.__token__ = token
def create_repo_extra(self, repo_name, description, organization_name, team_name):
try:
organization = self.__github__.get_organization(organization_name)
repo = organization.create_repo(name=repo_name,
description=description,
private=True,
has_issues=False,
has_wiki=False,
has_projects=False,
allow_merge_commit=False)
# do other things with the returned repo.....
return True
except GithubException as ex:
print(ex.data)
return False
Here is the test:
import unittest
from unittest.mock import patch, MagicMock, ANY
from github.Organization import Organization
from github.Repository import Repository
from src.github_service import GithubService
class TestGithubService(unittest.TestCase):
#patch('github.Organization.Organization.create_repo',
side_effect=MagicMock(return_value=Repository(ANY, {}, {}, True)))
#patch('github.MainClass.Github.get_organization',
return_value=MagicMock(return_value=Organization(ANY, {}, {}, True)))
def test_create_repo_returns_true(self, get_organization, create_repo):
sut = GithubService("token")
actual = sut.create_repo_extra('repo-name', 'description', 'organization-name', 'team-name')
get_organization.assert_called() # ok
create_repo.assert_called() # failed
self.assertTrue(actual)
Since you mock your Github.get_organization you can use the MagicMock it returns directly rather than trying to mock another layer.
In this, I patch the same Github.get_organization, but avoid giving it a side effect or return value, and therefore pass it as an arg (like you did).
Then I create a convenience mock_organization and it will be the return value of the patched Github.get_organization.
Finally, the patch is checked like you did, and through the convenience mock_organization I check the create_repo method is called as well.
class TestGithubService(unittest.TestCase):
#patch("github.MainClass.Github.get_organization")
def test_create_repo_returns_true(self, mock_get_organization):
mock_organization = MagicMock()
mock_get_organization.return_value = mock_organization
sut = GithubService("token")
actual = sut.create_repo_extra(
"repo-name", "description", "organization-name", "team-name"
)
mock_get_organization.assert_called() # ok
mock_organization.create_repo.assert_called() # ok
self.assertTrue(actual)
Without seeing more of your code I am not sure why patching Organization did not work, but this is simpler, cleaner and just as effective.

How to mock bucket.objects.filter() from boto3 resource?

I am having some problems mocking the bucket.objects.filter() method, but I am able to mock most other boto3 calls.
I have a class that is similar to this code in a file with the path my_project.utils.s3_api:
from boto3.session import Session, Config
class S3Resource:
def __init__(self, kwargs):
session = Session()
self.client = session.resource(
's3',
aws_access_key_id=kwargs['access_key'],
aws_secret_access_key=kwargs['secret_key'],
endpoint_url=kwargs['s3_url'],
region_name=kwargs['region'],
config=Config(signature_version='s3v4'
)
self.kwargs = kwargs
<many methods that are already successfully tested here>
# this is the method that I cannot test correctly:
def list_bucket_contents(self):
bucket = self.client.Bucket(self.kwargs['bucket'])
return [summary.key for summary in bucket.objects.filter()]
Then in my tests file I have something like this:
#mock.patch('my_project.utils.s3_api.Session.resource')
def test_list_bucket_contents(self, mock_connection):
ObjectSummary = namedtuple('ObjectSummary', 'bucket_name key')
obj_collection = (
ObjectSummary(bucket_name='mybucket', key='file1.txt'),
ObjectSummary(bucket_name='mybucket', key='file2.txt'),
ObjectSummary(bucket_name='mybucket', key='file3.txt')
)
mock_client = mock.MagicMock()
mock_client.filter.return_value = obj_collection
mock_connection.return_value = mock_client
s3_client = S3Resource(**self.init_args)
s3_client.list_bucket_contents()
print(result)
The returned list is always empty.
The namedtuple part is just an attempt to mimic the bucket.objects.
I would be open to solutions using botocore Stub, but I cannot use a third party library like moto. I just need to mock the call to bucket.objects.filter(). Thank you in advance.
Short answer: replace mock_connection.return_value = mock_client with mock_connection.return_value.Bucket.return_value.objects = mock_client in your test and it would work.
The reason is that self.client is session.resource that you mock, then you create a Bucket (which adds the Bucket.return_value to the mocked path), then you do .objects and only then do you apply the filter() (adds filter.return_value which you already had).
A simple approach that can help you in future cases is to use a helper library I wrote to generate the asserts for you: the mock-generator.
To use it in your case, add mock_autogen.generate_asserts(mock_connection) right after your call to list_bucket_contents, like so:
import mock_autogen
#mock.patch('my_project.utils.s3_api.Session.resource')
def test_list_bucket_contents(self, mock_connection):
ObjectSummary = namedtuple('ObjectSummary', 'bucket_name key')
obj_collection = (
ObjectSummary(bucket_name='mybucket', key='file1.txt'),
ObjectSummary(bucket_name='mybucket', key='file2.txt'),
ObjectSummary(bucket_name='mybucket', key='file3.txt')
)
mock_client = mock.MagicMock()
mock_client.filter.return_value = obj_collection
mock_connection.return_value = mock_client
s3_client = S3Resource(**self.init_args)
s3_client.list_bucket_contents()
mock_autogen.generate_asserts(mock_connection)
It would print all the needed asserts for mock_connection, which would tell you the exact methods used. In your case, the input would contain the following line:
mock_connection.return_value.Bucket.return_value.objects.filter.assert_called_once_with()
From this line you can derive the path you need to mock and replace with the filter objects.

How to Mock Attributes & Properties of Return Values with patch()

test_client/wclient.py
import json
import requests
client = requests.session()
def setup():
response = REST_CLIENT.post(
"https://placeholder.com",
auth=(placeholder, placeholder),
data={"grant_type": "client_credentials"},
)
status_code = response.status_code
if status_code in OK_STATUS:
payload = json.loads(response.content, object_pairs_hook=OrderedDict)
else:
payload = response.text
msg = (
"Status Code %s" % status_code
)
logger.error(msg)
raise ValueError(msg)
return payload["access_token"]
Test File:
test_client/test_client.py
import mock
import wclient
#mock.patch("test_client.wclient")
def test_taxes_pitney_bowes_setup_success(resp):
resp.return_value.post.return_value.status_code = "200"
wclient.pitney_bowes_setup()
Status Code <MagicMock name='REST_CLIENT.post().status_code' id='4868492200'>
How can I mock the module's methods and attributes with mock.patch()? I've read through pages of stack overflow posts but I'm getting confused with all the different ways to enforce a magic mock.
I've tried mocking:
resp.return_value.post.return_value.status_code
resp.return_value.post.return_value.status_code.return_value
resp.post.return_value.status_code
resp.post.return_value.status_code.return_value
resp.post.status_code
resp.post.status_code.return_value
I think there are numerous ways of actually doing the mock (see the many methods in Mocking Method Calls In Python). The way I like to do it and find easy for simple mocks is:
For functions:
#patch('module.print', lambda x: None)
For attributes:
#patch('module.cwd', os.path.join(os.getcwd(), "folder"))
This blog post might be helpful for you: https://medium.com/uckey/how-mock-patch-decorator-works-in-python-37acd8b78ae.
Let me know if you have more questions.
Edit:
To add multiple mocks just add another attribute:
import wclient
#mock.patch("test_client.wclient")
#mock.patch("another_attribute", "value")
#mock.patch("another_function", lambda x, y: x + y)
def test_taxes_pitney_bowes_setup_success(resp):
resp.return_value.post.return_value.status_code = "200"
wclient.pitney_bowes_setup()

pytest passing data for cleanup

I'm writing tests for a post api, which returns the resource that gets created. But how do I pass this data to a fixture in python so it can cleanup after the test is completed
Cleanup:
#pytest.fixture(scope='function')
def delete_after_post(request):
def cleanup():
// Get ID of resource to cleanup
// Call Delete api with ID to delete the resource
request.addfinalizer(cleanup)
Test:
def test_post(delete_after_post):
Id = post(api)
assert Id
What is the best way to pass the response(ID) back to to the fixture for the cleanup to kick in. Don't want to do the cleanup as part of the test.
You can access that ID using request instance and use anywhere in your code by request.instance.variableName. Like, Suppose your method for deleting id delete(resource_id), here
conftest.py
import pytest
#pytest.fixture(scope='function')
def delete_after_post(request):
def cleanup():
print request.node.resourceId
# Get ID of resource using request.instance.resourceId
# Call Delete api with ID to delete the resource
request.addfinalizer(cleanup)
test file xyz_test.py
def test_post(delete_after_post,request):
request.node.resourceId='3'
I created a fixture that collects cleanup functions for this purpose:
import pytest
#pytest.fixture
def cleaner():
funcs = []
def add_func(func):
funcs.append(func)
yield add_func
for func in funcs:
func()
def test_func(cleaner):
x = 5
cleaner(lambda: print('cleaning', x))
This way you don't need a separate fixture for each use case.
The way I did was create a class called TestRunContext and set static variables to pass around data.
File: test_run_context.py
class TestRunContext:
id_under_test = 0
File: conftest.py
#pytest.fixture(scope='function')
def delete_after_post():
print('hello')
yield
url = 'http://127.0.0.1:5000/api/centres/{0}'.format(TestRunContext.id_under_test)
resp = requests.delete(url)
File: test_post.py
def test_creates_post(delete_after_post):
post_data ={
'name' : 'test',
'address1': 'test',
'city': 'test',
'postcode': 'test',
}
url = 'http://127.0.0.1:5000/api/centres'
data = requests.post(url, post_data)
TestRunContext.id_under_test = data.id
assert data
This works for me for now. But hoping to find a better solution than using ContextManager file. Really dont like this solution.

How to import own module for mocking? (import error: no module named my_module!)

I want to unit test my class, which is in another file named client_blogger.py.
My unit test file, is in the same directory. All of my other unit tests work, except when I try to mock one of my own methods.
## unit_test_client_blogger.py
import mock
import json
from client_blogger import BloggerClient, requests
Class TestProperties():
#pytest.fixture
def blog(self):
return BloggerClient(api_key='123', url='http://example.com')
#mock.patch('client_blogger._jload')
#mock.patch('client_blogger._send_request')
def test_gets_blog_info(self, mock_send, mock_jload):
""" Get valid blog info from API response. """
valid_blog_info = 'some valid json api response here'
parsed_response = json.loads(valid_blog_info)
correct_blog_id = '7488272653173849119'
mock_jload.return_value = valid_blog_info
id = self.blog().get_blog_info(parsed_response)
assert id == correct_blog_id
And here is the client_blogger.py file contents:
# client_blogger.py
import requests, json
class BloggerClient(object):
""" Client interface for Blogger API. """
def __init__(self, key, url):
# removed some code here for brevity
def _send_request(self, api_request):
""" Sends an HTTP get request to Blogger API.
Returns HTTP response in text format. """
# snip
def _jload(self, api_response):
""" Accepts text API response. Returns JSON encoded response. """
# snip
def get_blog_info(self):
""" Makes an API request. Returns Blog item information. """
request = '{b}/blogs/byurl?url={u}&key={k}'.format(b=self.base, u=self.url, k=self.key)
txt_response = self.send_request(request)
response = self._jload(txt_response)
return response['id']
I want to mock out self.send_request() and self._jload() method calls in the above method.
But Mock module complains: ImportError: No module named client_blogger.
The error must lie here:
#mock.patch('client_blogger._jload')
#mock.patch('client_blogger._send_request')
I've tried many variations in order to get mock.patch to find my module or class. But none of them have worked.
I've tried the following:
#mock.patch('client_blogger.BloggerClient._jload')
#mock.patch('BloggerClient._jload')
#mock.patch('._jload')
None of those work. Any idea how to mock.patch a method from my own module?
(It seems strange, because I can mock.patch other modules, just not my own :-s)
You want this:
#mock.patch('client_blogger.BloggerClient._jload')
#mock.patch('client_blogger.BloggerClient._send_request')
def test_gets_blog_info(self, mock_send, mock_jload):
""" Get valid blog info from API response. """
valid_blog_info = 'some valid json api response here'
parsed_response = json.loads(valid_blog_info)
correct_blog_id = '7488272653173849119'
mock_jload.return_value = valid_blog_info
id = self.blog().get_blog_info(parsed_response)
assert id == correct_blog_id
The BloggerClient implementation is coming from the client_blogger module, so you need to patch client_blogger.BloggerClient. You list that as one of the things you tried that doesn't work, but I just tried it, and it works fine for me. What issue did you have when you tried that?
You need to include the file name of the class in the path, before the object name.
e.g. if I have a method named foo in a class named Event in tools/event.py the path will be as follows:
patch("tools.event.Event.foo", boo)
For python3, the format is as follows:
from unittest.mock import patch
#patch('client_blogger.BloggerClient._jload')
.
.
.
Docs: https://docs.python.org/3/library/unittest.mock.html#patch
This is very, very important:
patch() is straightforward to use. The key is to do the patching in the right namespace. See the section where to patch.

Categories

Resources