How to mock HTTPError response in Python unit test - python

I would like to write a unit test case for HTTPError exception part based on the error response content I get. But I have now idea how I can mock the response so that the unit test can reach doSomething1() instead of doSomething2().
foo.py
def get_result_from_API():
#Try to call API here...
def getSomething():
try:
result = get_result_from_API()
except HTTPError as error:
error_json = error.response.json()
if error_json.get("error").get("code") == "00001":
doSomething1()
else:
doSomething2()
raise error
Unit Test
#patch('foo.doSomething2')
#patch('foo.doSomething1')
#patch('foo.get_result_from_API')
def testGetSomething(get_result_from_API,doSomething1,doSomething2):
mock_response = Mock()
mock_response.return_value = {
"error":{
"code": "00001",
"message": "error message for foo reason"
}
}
get_result_from_API.side_effect = HTTPError(response=mock_response)
with self.assertRaises(HTTPError):
foo.getSomething()
doSomething1.assert_called_once()
The current result is that doSomething1() is not called where as doSomething2() is called.

Since the getSomething function calls:
error_json = error.response.json()
the solution is to mock the call to .json()
mock_response.json.return_value = {
"error":{
"code": "00001",
"message": "error message for foo reason"
}
}
As it was originally written, getSomething would have had to have called response() directly.
error_json = error.response()
Here is a more generic example:
>>> from unittest.mock import Mock
>>> mock_response = Mock()
>>> mock_response.return_value = {"hello": "world"}
>>> mock_response() # call response() directly
{'hello': 'world'}
>>> mock_response.json() # no return_value is assigned to the json() call
<Mock name='mock.json()' id='4407756744'>
>>> mock_response.json.return_value = {"hello": "universe"} # assign a json() return_value
>>> mock_response.json()
{'hello': 'universe'}
If you want, you can even do this while instantiating Mock:

Related

How do I mock a method that uses requests.get with the WITH keyword in my class?

I am having trouble understanding how mocking works when the get responses involve using the with keyword. Here is an example I am following for my class `Album' and I have been successful when I am mocking a url as seen below:
def find_album_by_id(id):
url = f'https://jsonplaceholder.typicode.com/albums/{id}'
response = requests.get(url)
if response.status_code == 200:
return response.json()['title']
else:
return None
Here the test
class TestAlbum(unittest.TestCase):
#patch('album.requests')
def test_find_album_by_id_success(self, mock_requests):
# mock the response
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
'userId': 1,
'id': 1,
'title': 'hello',
}
# specify the return value of the get() method
mock_requests.get.return_value = mock_response
# call the find_album_by_id and test if the title is 'hello'
self.assertEqual(find_album_by_id(1), 'hello')
However, I am trying to understand how this would work with the with keyword involved in the code logic which I am using in my project. This is how I changed the method
def find_album_by_id(id):
url = f'https://jsonplaceholder.typicode.com/albums/{id}'
with requests.get(url) as response:
pipelines = response.json()
if response.status_code == 200:
return pipelines['title']
else:
return None
Here is my current test:
#patch('album.requests.get')
def test_find_album_by_id_success(self, mock_requests):
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.pipelines.response.json = {
'userId': 1,
'id': 1,
'title': 'hello',
}
mock_requests.return_value.json.return_value = mock_response
self.assertEqual(find_album_by_id(1), 'hello')
Thanks
I have tried debugging the test and it just never receives the status code of 200 so I am not sure I am mocking response correctly at all? From my understanding the mock_response is supposed to have that status code of 200 but breakline indicates that response is just a MagicMock with nothing in it.

Monkeypatching/mocking the HTTPX external requests

I'm trying to monkeypatch the external request. Here is the code of a web endpoint:
import httpx, json
...
#app.get('/test')
async def view_test(request):
async with httpx.AsyncClient() as client:
# sending external request
api_response = await client.get(
f'https://jsonplaceholder.typicode.com/todos/1',
timeout=10,
)
resp = api_response.json()
# modifying the result
resp['foo'] = 0
# forwarding the modified result back to the user
return HTTPResponse(json.dumps(resp), 200)
When user sends a GET request to /test, it requests an external API (JSONPlaceholder), gets the JSON result and adds 'foo' = 0 to it. After that it forwards the result back to the user.
Here is the Postman result:
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false,
"foo": 0
}
Next, here is my pytest code:
import httpx, pytest
...
# The `client` parameter is the fixture of web app
def test_view_test(client, monkeypatch):
async def return_mock_response(*args, **kwargs):
return httpx.Response(200, content=b'{"response": "response"}')
monkeypatch.setattr(httpx.AsyncClient, 'get', return_mock_response)
_, response = client.test_client.get('/test')
assert response.json == {'response': 'response', 'foo': 0}
assert response.status_code == 200
I used pytest's monkeypatch fixture to mock the HTTPX request's result with {"response": "response"}.
So basically what I expected is that endpoint adds 'foo' = 0 to my mocked result. But instead it returned {"response": "response"} unmodified.
Here's the traceback of pytest -vv command:
> assert response.json == {'response': 'response', 'foo': 0}
E AssertionError: assert {'response': 'response'} == {'response': 'response', 'foo': 0}
E Common items:
E {'response': 'response'}
E Right contains 1 more item:
E {'foo': 0}
E Full diff:
E - {'foo': 0, 'response': 'response'}
E ? ----------
E + {'response': 'response'}
Can someone help me with why the endpoint doesn't modify httpx.AsyncClient().get mocked result?
I used sanic==22.9.0 for backend, httpx==0.23.0 for requests, and pytest==7.2.0 for testing.
Expected to get {'response': 'response', 'foo': 0} instead got {"response": "response"} - an unmodified result of mocked httpx response.
The issue is that sanic-testing uses httpx under the hood. So, when you are monkeypatching httpx you are also impacting the test client. Since you only want to mock the outgoing calls we need to exempt those from being impacted.
My comment to #srbssv on Discord was to monkeypatch httpx with a custom function that would inspect the location of the request. If it was the internal Sanic app, proceed as is. If not, then return with a mock object.
Basically, something like this:
from unittest.mock import AsyncMock
import pytest
from httpx import AsyncClient, Response
from sanic import Sanic, json
#pytest.fixture
def httpx():
orig = AsyncClient.request
mock = AsyncMock()
async def request(self, method, url, **kwargs):
if "127.0.0.1" in url:
return await orig(self, method, url, **kwargs)
return await mock(method, url, **kwargs)
AsyncClient.request = request
yield mock
AsyncClient.request = orig
#pytest.fixture
def app():
app = Sanic("Test")
#app.post("/")
async def handler(_):
async with AsyncClient() as client:
resp = await client.get("https://httpbin.org/get")
return json(resp.json(), status=resp.status_code)
return app
def test_outgoing(app: Sanic, httpx: AsyncMock):
httpx.return_value = Response(201, json={"foo": "bar"})
_, response = app.test_client.post("")
assert response.status == 201
assert response.json == {"foo": "bar"}
httpx.assert_awaited_once()

Pytest mock.patch requests AttributeError: does not have the attribute 'json'

I'm trying to test an api mock call using from unittest.mock import patch. I keep getting an AttributError when I include .json() in my function's response return (i.e. return response.json()).
When I exclude the .json() in my return statement, the test passes. I'm pretty new to testing and I can't figure out how to get around this error. Does anyone have an idea of how to get around this by keeping the .json() in the return statment?
Here is my code for reference:
test_pytest.py
from unittest.mock import patch
from src.open_play_helper import get_open_play_data
#patch('requests.get',
return_value={
"version": "v1",
"greeting": "Aloha ๐Ÿ‘‹"
}
)
def test_get_open_play_data(mock_get, mock_json):
print("Mock_get: ", mock_get())
print("Mock_json: ", mock_json())
print("Function: ", get_open_play_data)
# print("Function call: ", get_open_play_data('https://connect.secretlyportal.com/v1/',
# OP_CONFIG['OP_KEY'],
# OP_CONFIG['OP_PASSWORD']))
result = get_open_play_data(
'route', 'key', 'password'
)
print("Result: ", result)
assert mock_get() == result
assert False
open_play_helper.py
import requests
def get_open_play_data(route, key, password) -> object:
# route should be a string
try:
response = requests.get(
route,
auth=(
key,
password
)
)
print("Response: ", response)
return response.json()
except requests.exceptions.RequestException as e:
print(f"There was a problem with your request: '{e}'")
As #LuรญsMรถllmann already pointed out, the patching is incorrect. The actual usage was:
requests.get().json()
But the patching was:
requests.get.return_value = {some dict}
This means that requests.get() will already return {some dict} which then fails when .json() is called.
Solution 1
The dictionary response must be mocked at requests.get.return_value.json.return_value and not just the requests.get.return_value:
#patch('requests.get')
def test_get_open_play_data(mock_get):
mock_json = {
"version": "v1",
"greeting": "Aloha ๐Ÿ‘‹"
}
mock_get.return_value.json.return_value = mock_json
result = get_open_play_data(
'route', 'key', 'password'
)
print("Result: ", result)
assert mock_json == result
Solution 2 (Recommended)
Don't reinvent the wheel of mocking the requests module. Use a library such as requests_mock which does it easily for you.
import requests_mock as requests_mock_lib
def test_get_open_play_data_using_lib(requests_mock):
mock_json = {
"version": "v1",
"greeting": "Aloha ๐Ÿ‘‹"
}
requests_mock.get("http://route", json=mock_json) # If you want the mock to be used on any URL, replace <"http://route"> with <requests_mock_lib.ANY>
result = get_open_play_data(
'http://route', 'key', 'password'
)
print("Result: ", result)
assert mock_json == result
Your mock returns a dictionary, not a response object like the original requests.get function:
#patch('requests.get',
return_value={
"version": "v1",
"greeting": "Aloha ๐Ÿ‘‹"
}
)
So basically, you're doing:
{"version":"v1","greeting":"Aloha ๐Ÿ‘‹"}.json()
Hence the error:
'dict' object does not have the attribute 'json'

Response generated while mocking is not correct in python tests

I am trying to unit test some of my functions using mocks in python. Few of them seems to be working as expected but I am trouble with the response of one the test case. Which it not returning the ideal response which it should.
###############motpenrichreceiverinfo.py##################
import requests
def getReceiverInfo(fundId):
print('Inside fetchReceiverInfo',fundId)
response = requests.get("REST URL FOR RECEIVER INFO")
if response.ok:
return response
else:
return None
def getMotpTrade(trade_id):
response = requests.get("REST URL")
if response.ok:
return response
else:
return None
def getMotpCanonical(trade_id):
response = requests.get("REST URL")
if response.ok:
return response
else:
return None
def EnrichTradeWithReceiverInfo(event, context):
#print('Lambda function started..')
trade_id = event['tradeId']
motpTrade = getMotpTrade(trade_id)
canonicalTrade = getMotpCanonical(trade_id)
fundId = motpTrade['Account']
#print(fundId)
data = getReceiverInfo(fundId)
print(data)
return data
##########################test_motptrade.py##############################
# Standard library imports
from unittest.mock import Mock, patch
# Third-party imports...
from nose.tools import assert_true
from nose.tools import assert_is_not_none, assert_list_equal, assert_dict_equal
# Local imports
from motpenrichreceiverinfo import getMotpTrade, getReceiverInfo, EnrichTradeWithReceiverInfo
#patch('motpenrichreceiverinfo.requests.get')
def test_motptraderequest_response(mock_get):
motpTrade = [{
"motpTrade":"motpTrade"
}]
# Configure the mock to return a response with an OK status code.
mock_get.return_value.ok = True
mock_get.return_value.json.return_value = motpTrade
# Send a request to the API server and store the response.
response = getMotpTrade('tradeId')
# If the request is sent successfully, then I expect a response to be returned.
assert_list_equal(response.json(), motpTrade)
#patch('motpenrichreceiverinfo.requests.get')
def test_getReceiverInfo_respose_ok(mock_get):
receiverInfo = [{
"reciever":"reciever"
}]
# Configure the mock to return a response with an OK status code.
mock_get.return_value.ok = True
mock_get.return_value.json.return_value = receiverInfo
# Send a request to the API server and store the response.
response = getReceiverInfo("1110")
# If the request is sent successfully, then I expect a response to be returned.
assert_list_equal(response.json(), receiverInfo)
#patch('motpenrichreceiverinfo.getMotpTrade')
#patch('motpenrichreceiverinfo.getMotpCanonical')
#patch('motpenrichreceiverinfo.getReceiverInfo')
def test_EnrichTradeWithReceiverInfo_ok(mock_get,mock_canonical,mock_receiverinfo):
motpTrade = [{
"motpTrade":"motpTrade"
}]
receiverInfo = [{
"reciever":"reciever"
}]
event = {"tradeId":"123456"}
# Configure the mock to return a response with an OK status code.
mock_get.return_value = Mock(ok=True)
mock_get.return_value.json.return_value = motpTrade
mock_canonical.return_value.ok = True
mock_canonical.return_value.json.return_value = [{}]
mock_receiverinfo.return_value.ok = True
mock_receiverinfo.return_value.json.return_value = receiverInfo
data = EnrichTradeWithReceiverInfo(event,"")
assert_true(mock_get.called)
assert_true(mock_receiverinfo.called)
assert_list_equal(data.json(),receiverInfo)
Here my first two cases are working as expected. But my last test case(test_EnrichTradeWithReceiverInfo_ok) is somehow not working. Ideally it should have response equal to receiverInfo object.But when I run it gives below error. It would be really helpful someone can help me understand what I am doing wrong here.
(venv) C:\Development\motplambdas>nosetests -v test_motptrade.py
test_motptrade.test_EnrichTradeWithReceiverInfo_ok ... FAIL
test_motptrade.test_getReceiverInfo_respose_ok ... ok
test_motptrade.test_motptraderequest_response ... ok
======================================================================
FAIL: test_motptrade.test_EnrichTradeWithReceiverInfo_ok
----------------------------------------------------------------------
Traceback (most recent call last):
File "c:\development\motplambdas\venv\lib\site-packages\nose\case.py", line 198, in runTest
self.test(*self.arg)
File "c:\program files\python38\lib\unittest\mock.py", line 1325, in patched
return func(*newargs, **newkeywargs)
File "C:\Development\motplambdas\test_motptrade.py", line 69, in test_EnrichTradeWithReceiverInfo_ok
assert_list_equal(data.json(),receiverInfo)
AssertionError: Lists differ: [{'motpTrade': 'motpTrade'}] != [{'reciever': 'reciever'}]
First differing element 0:
{'motpTrade': 'motpTrade'}
{'reciever': 'reciever'}
- [{'motpTrade': 'motpTrade'}]
+ [{'reciever': 'reciever'}]
----------------------------------------------------------------------
Ran 3 tests in 0.011s
FAILED (failures=1)
The issue is the order of which you are passing your Mock objects into the function call.
#patch('motpenrichreceiverinfo.getMotpTrade')
#patch('motpenrichreceiverinfo.getMotpCanonical')
#patch('motpenrichreceiverinfo.getReceiverInfo')
def test_EnrichTradeWithReceiverInfo_ok(mock_get,mock_canonical,mock_receiverinfo)
Mock objects are passed from the bottom up, meaning #patch('motpenrichreceiverinfo.getReceiverInfo') is the first Mock passed in to the function call, not the last as you have it listed. Due to this you end up setting getReceiverInfo to return the wrong value. The solution is to switch the function call to look like this:
def test_EnrichTradeWithReceiverInfo_ok(mock_receiverinfo, mock_canonical, mock_get)
You can read more about this here where it explains how nesting decorators works.
Note that the decorators are applied from the bottom upwards. This is the standard way that Python applies decorators. The order of the created mocks passed into your test function matches this order.

Django unittest with not raising exception as expected with assertRaises()

I'm writing a unittest class to ensure a method tests for a success, and then tests for an Exception. I'm passing a response that should trigger the exception, but in the testing method it does not get raised. Of note, I can manually make the exception raise in the actual method.
Test class:
class TestAPI(TestCase):
def test_send_method(self):
with mock.patch('requests.post') as mock_request:
mock_response = mock.Mock()
mock_response.json.return_value = {
"success": "true"
}
mock_request.return_value = mock_response
send_method() // THIS WORKS NICELY
# Test that errors from the API are handled correctly.
with self.assertRaises(SendException):
mock_response.status_code = 500
mock_response.json.return_value = {
'errors': 'An error has occurred.',
}
send_method() // THIS RAISES NO EXCEPTION
As I said, It's odd because I can manually trigger the 500 status code in the actual method and it raises fine. I can even change the initial mock response success to err and it will raise in the actual method. Why would it not raise in the unittest?
Method being tested:
class SendException(Exception):
pass
def send_method():
session_headers = {
"Content-Type": "application/json",
}
session_body = {
"send": "yes"
}
session_url = u'{}'.format(URL)
session_response = requests.post(session_url, json=session_body, headers=session_headers)
try:
if(session_response.json().get('errors') is not None):
raise SendException(
'Returned error with status {}: {}'.format(
session_response.status_code,
session_response.json().get('errors')
)
)
except ValueError as err:
raise SendException(
'Responded with non-json and status code {}. Error: {} - Response Text: {}'.format(
session_response.status_code,
err,
session_response.text
)
)

Categories