I'm writing some tests and I'd like to convert flask.Response objects to corresponding requests.Response objects. So I have 2 Flask apps (e.g. A and B), and A makes internal calls to B (via requests.post(url, json=payload)). My goal is to properly mock those calls without even launching any servers, and the current solution looks like the following:
from unittest import mock
...
def mock_B_request(url, json):
response = app_B.test_client().post(url, json=json) # flask.Response
# Some hacking should be done here,
# since flask.Respone doesn't have `.ok`, `.json()`, etc.,
# so it will break the code inside app_A
return response
...
# Inside the actual test method
with mock.patch('requests.post', side_effect=mock_B_request):
response = app_A.test_client().post(url, json=payload)
result = response.get_json()
...
Has someone already encountered with such a problem? What is the easiest solution here?
Eventually, I have found a very easy working solution:
def mock_B_request(url, **kwargs):
response = app_B.test_client().post(url, json=kwargs['json'])
# Hack a bit in order to make flask.Response
# support the same API as requests.Response
response_mock = mock.MagicMock()
response.status_code = response.status_code
response_mock.ok = response.status_code == 200
response_mock.json = lambda: response.get_json()
return response_mock
I hope it will be helpful for somebody.
Related
I would like to get all the GitHub branches of a certain repository. Using the GitHub API as documented here, https://developer.github.com/v3/repos/branches/#list-branches I try to use the GET request documented.
However, when I try unit testing, the response always evaluates to None and I get error: AttributeError: 'NoneType' object has no attribute 'json'. I am unsure if my issue is with the API call, or I am testing the call. See code below:
#staticmethod
def get_branches(git_base, org, repo):
github_repo_url = f"https://{git_base}/api/v3/repos/{org}/{repo}"
collected_all_branches: bool = False
page = 1
github_branches = []
# collect all branches in repo
while not collected_all_branches:
response = (requests.get(f"{github_repo_url}/branches/?per_page=100&page={page}",
headers=HEADERS))
if len(response.json()) == 0:
collected_all_branches = True
else:
github_branches.extend([response["name"] for response in response.json()])
page = page + 1
return github_branches
Unit Test:
#patch.object(requests, "get", return_value=None)
def test_get_branches(self, mock_get):
r = GithubService.get_branches("test", "test", "test")
mock_get.assert_called_with("test", "test", "test")
The test is not set up correctly. Since the unit test verifies the arguments of the requests.get() call you can modify it like this:
#patch("requests.get")
def test_get_branches(self, mock_get):
# Give the actual HEADERS you use in the get_branches() function call, for example something like:
HEADERS = {"Accept": "application/vnd.github+json"}
r = GithubService.get_branches("test", "test", "test")
mock_get.assert_called_with("https://api.github.com/repos/test/test/branches/?per_page=100&page=1", headers=HEADERS)
The test will now pass successfully.
Also, the Github API call is incorrect. The documentation you have linked to says the URL must be of this format:
https://api.github.com/repos/OWNER/REPO/branches
So in get_branches() make the following change:
github_repo_url = f"https://api.github.com/repos/{org}/{repo}"
I have a view that perform a third party API call:
from .atoca_api_calls import atoca_api_call
def atoca_api_call_view(request):
# some code
data = atoca_api_call(**params) # the actual API call happens in this func
# some code
My intention is to mock just the atoca_api_call() func and not the whole view.
class AtocaTestCase(TestCase):
def setUp(self):
# some code
#patch('crm.atoca_api_calls.atoca_api_call')
def test_atoca_call(self, mock_atoca_api_call):
mock_atoca_api_call.return_value = MagicMock(
status_code=200,
response=some_json # just returns some json
)
url = reverse('crm:atoca-api-call') # url related to `atoca_api_call_view`
response = self.client.post(url, some_others_params)
# various asserts
The test works fine but atoca_api_call() is not mocked.
I'm aware of where to patch:
#patch('crm.views.atoca_api_call', autospec=True)
Raises a ValueError:
ValueError: Failed to insert expression "<MagicMock name='mock.__getitem__().__getitem__().__getitem__()().resolve_expression()' id='140048216397424'>" on crm.Company.atoca_id. F() expressions can only be used to update, not to insert.
It's probably a simple issue I don't catch for inexperience, any help is really appreciated.
As i see the atoca_api_call() it is mocked. The issue is the return value.
mock_atoca_api_call.return_value = MagicMock(
status_code=200,
response=some_json # just returns some json
)
This should ve a proper response i assume a JsonResponse or Response(data) not sure what are you returning. Try with:
mock_atoca_api_call.return_value = JsonResponse(
some_json # just returns some json
)
I am working with an API and parsing different informations on its response, and calling the parsed information in different functions / files.
The issue is that it will call quickInfo() multiple times as a result, creating multiple API requests, which is unwanted as there is a rate limit or cause performance issues (API response is very large).
I am trying to find a way to get the API once and then be able to use the content of the response in different situations.
I could make "reponse" a global variable but I read that it was bad programming and could cause memory leaks.
Simplified code is as follows:
FILE 1
def quickInfo(name):
response = requests.get('[website]/product/{}?token=No'.format(name), headers=headers, verify=False).json()
return response
def parsing(name):
r = quickInfo(name)
name = "{}".format(r["product"]["name"])
buyprice_raw = [i["buyprice"] for i in r["avgHistory"]]
buy_orders = "{:,}".format(r["product"]["buyorders"])
sell_orders = "{:,}".format(r["product"]["sellorders"])
return name, buyprice_raw, buy_orders, sell_orders
def charting(name):
buyprice, sellprice = parsing(name)
#code continues
FILE 2
name, price = parsing(name)
print(name +"'s price is of " + price) #sample use
#code continues
Thanks for your help!
The absolute easiest way would be to decorate quickInfo with the #functools.lru_cache() decorator, but you'll just have to be aware that it will only ever do a real request once per name (unless you clear the cache):
import functools
#functools.lru_cache()
def quickInfo(name):
response = requests.get(
"[website]/product/{}?token=No".format(name),
headers=headers,
verify=False,
)
response.raise_for_status()
return response.json()
I already know that one can implement a class that inherits from SimpleTestCase, and one can test redirection by:
SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, host=None, msg_prefix='', fetch_redirect_response=True)
However, I am wondering what is the way I can check for redirection using pytest:
#pytest.mark.django_db
def test_redirection_to_home_when_group_does_not_exist(create_social_user):
"""Some docstring defining what the test is checking."""
c = Client()
c.login(username='TEST_USERNAME', password='TEST_PASSWORD')
response = c.get(reverse('pledges:home_group',
kwargs={'group_id': 100}),
follow=True)
SimpleTestCase.assertRedirects(response, reverse('pledges:home'))
However, I am getting the following error:
SimpleTestCase.assertRedirects(response, reverse('pledges:home'))
E TypeError: assertRedirects() missing 1 required positional argument: 'expected_url'
Is there any way I can use pytest to verify redirection with Django? Or I should go the way using a class that inherits from SimpleTestCase?
This is an instance method, so it will never work like a class method. You should be able to simply change the line:
SimpleTestCase.assertRedirects(...)
into:
SimpleTestCase().assertRedirects(...)
i.e. we're creating an instance in order to provide a bound method.
I'm not an expert with pytest so probably is not elegant but you can check for the Location header like
test_whatever(self, user):
client = Client()
url = reverse('admin:documents_document_add')
client.force_login(user)
response = client.post(url, {<something>})
# if the document is added correctly we redirect
assert response.status_code == 302
assert response['Location'] == reverse('admin:documents_document_changelist')
I am using bottle framework. I have code like
from bottle import request
def abc():
x = request.get_header('x')
...
...
data = request.json()
...
...
I am writing UTs for this function, I want to mock get_header and json of bottle.request, and return my mock data from that.
I tried.
from mock import patch
#patch('bottle.request.headers', return_value={'x': 'x'})
#patch('bottle.request.json', return_value=...)
def test_abc(self, _, __):
...
...
But it gives error for request.headers is read-only. I also have to mock request.json.
Thanks for help in advance :).
Check the source code of bottle, headers and json are like:
#DictProperty('environ', 'bottle.request.headers', read_only=True)
def headers(self):
''' A :class:`WSGIHeaderDict` that provides case-insensitive access to
HTTP request headers. '''
return WSGIHeaderDict(self.environ)
So in my pytest cases, I patched request.environ like below:
def test_xxx(monkeypatch):
monkeypatch.setitem(request.environ, 'bottle.request.json', {'name': 'xxx', 'version': '0.1'})
add_xxx()
assert
An easy alternative, to mock a bottle request, could be to inject it into your function:
from bottle import request
def abc(_request=None):
if _request is not None:
request = _request
x = request.get_header('x')
...
...
data = request.json()
...
...
This should be safe as your test code can call your view with a fake request object directly and your production code will skip the conditional.
I am NOT sure how this works with url routes with named params, as I've never used bottle.
Use Boddle https://github.com/keredson/boddle
def test_abc(self, _, __):
with boddle(headers={'x':'x'}):
# tests