Pytest - how to structure the tests efficiently - python

Suppose there are 2 endpoints to be tested.
Endpoint 1 returns data that needs to be used in a request to endpoint 2.
How to get 2 endpoints tested efficiently?
# file_1.py
def test_endpoint_1():
r = requests.get(...)
assert r.status_code == 200
maybe some JSON Schema validation
return r
# file_2.py
from file_1 import test_endpoint_1
def test_endpoint_2():
needed_data = test_endpoint_1()
r = requests.get(..., payload=needed_data.json())
assert r.status_code == 200
maybe some JSON Schema validation
Above approach kinda works but if I execute the test suite we are testing endpoint_1 twice. I could change the name of test_endpoint_1() to avoid that but maybe there is an easier and more elegant way?

Accessing endpoint1 is just one way to get a suitable payload for endpoint2. Another is to just provide a hard-coded payload.
# file_2.py
def test_endpoint_2():
needed_data = {"foo": "bar"} # Whatever is appropriate
r = requests.get(..., payload=needed_data.json())
assert r.status_code == 200
maybe some JSON Schema validation

Hard coded value
Generally, we "assume" that dependencies are working correctly.
In this case, keep it simple and create an hardcoded value in the test.
# file_2.py
def test_endpoint_2():
needed_data = "JSON" # valid return example from test_endpoint_1
r = requests.get(..., payload=needed_data)
assert r.status_code == 200
maybe some JSON Schema validation
pytest.fixture
If you need this value in many tests, create a fixture instead. This will allows you to edit the "valid value" for endpoint_2 in one place instead of changing it in every tests that depend on it.
I advise you to put your fixtures in a conftest.py file. This is explained here.
# conftest.py
import pytest
#pytest.fixture
def endpoint_1_value_200():
return "JSON"
# file_2.py
def test_endpoint_2(endpoint_1_value_200):
r = requests.get(..., payload=endpoint_1_value_200)
assert r.status_code == 200
maybe some JSON Schema validation

Just put it in a separate function :
def check_endpoint_1():
r = requests.get(...)
assert r.status_code == 200
maybe some JSON Schema validation
return r
def test_endpoint_1():
check_endpoint_1()
def test_endpoint_2():
needed_data = check_endpoint_1()
r = requests.get(..., payload=needed_data.json())
assert r.status_code == 200
maybe some JSON Schema validation

Related

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.

Mocking an API request for downstream module using Python

I am a beginner when it comes to writing tests and mocking.
I have a created two modules. One module object (Site) creates another object from my second module (Item) on init. The Item object makes a call to an API endpoint to get some data using requests.
I want to Mock the API call I am making so I can test things like a bad response and importantly have control over the response data.
I have simplified my code and put below. When I run the test I get back the actual response data and not what I have mocked.
I have a feeling I am not putting my Mock in the right place. Also, I have seen lots of people saying to use #unittest.patch annotation. I am not clear if I should be using that here.
So I am looking for how to get _get_range_detail to actually return a Mocked response from requests and also just general feedback on if it looks like I am approaching this the right way.
# hello_world.py
from mymodule.site import Site
sites = [
dict(
name="site1",
ranges=[
"range1",
"range2"
]
)
]
site_object = Site(sites[0]['name'], sites[0]['ranges'])
for i in site_object.get_ranges_objects():
print(i.range_detail)
# site.py
from mymodule.item import Item
class Site:
def __init__(self, name, ranges):
self.name = name
self.ranges = ranges
self.ranges_objects = []
for my_range in ranges:
self.ranges_objects.append(Item(my_range))
def get_ranges_objects(self):
return self.ranges_objects
# item.py
import requests
class Item:
def __init__(self, range_name):
self.range_name = range_name
self.range_detail = self._get_range_detail(self.range_name)
def _get_range_detail(self, range_name):
uri = "https://postman-echo.com/get?some_cool_value=real_value"
try:
r = requests.get(uri)
if r.status_code == 200:
return r.json()['args']
else:
return None
except Exception as e:
print(e)
exit(1)
# test_site.py
import pytest
from mymodule.site import Site
from unittest import mock
from mymodule.item import requests
def test_get_ranges_objects():
sites = [
dict(
name="site1",
ranges=[
"range1",
"range2"
]
)
]
requests = mock.Mock()
requests.status_code = 200
requests.json.return_value = {
'args': {'some_mock_value': 'mocky'}
}
site_object = Site(sites[0]['name'], sites[0]['ranges'])
assert site_object.name == "site1"
assert isinstance(site_object.ranges_objects, list)
assert site_object.ranges_objects[0].range_detail == dict(some_mock_value='mocky')
You can use pytest-mock. it makes mocking in pytest simple. (pip install pytest-mock)
You should replace requests.get. Simply
requests_get = mock.patch('requests.get').start()
If you use pytest-mock,
requests_get = mocker.patch('requests.get')
Rewrote test case using pytest-mock
# test_site.py
import pytest
from mymodule.site import Site
from unittest import mock
#pytest.fixture
def requests_get(mocker):
requests_get = mocker.patch('requests.get')
yield requests_get
def test_get_ranges_objects(mocker, requests_get):
response = mocker.MagicMock()
response.status_code = 200
response.json.return_value = {'args': {'some_mock_value': 'mocky'}}
requests_get.return_value = response
sites = [
dict(
name="site1",
ranges=[
"range1",
"range2"
]
)
]
site_object = Site(sites[0]['name'], sites[0]['ranges'])
assert site_object.name == "site1"
assert isinstance(site_object.ranges_objects, list)
assert site_object.ranges_objects[0].range_detail == {'some_mock_value': 'mocky'}

Python how to mock a function within another function

I have made some changes to a functionality which is breaking the unit tests. Previously i had a common.py containing the function request_url:
import requests
def request_url(method, url):
return _request_url(method, url)
def _request_url(method, url):
return requests.request(method, url)
And this was the test:
#mock.patch("requests.request", autospec=True)
def test_request_url_200(self, mock_request):
response = mock.MagicMock()
response.status_code = 200
method = mock.sentinel.method
path = "url"
result = common.request_url(
method,
path
)
self.assertListEqual([
mock.call(
mock.sentinel.method,
"url"
),
], list(mock_request.mock_calls))
self.assertListEqual([mock.call.raise_for_status()], list(response.mock_calls))
self.assertEqual(mock_request.return_value, result)
After the changes in functionality, instead of simply calling requests.request, i have first initiated a session, mounted a TLS Adapter and then called the session's request function like this:
def _request_url(method, url):
session = requests.session()
adapter = TlsAdapter(ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1)
session.mount("https://", adapter)
return response = session.request(method, url)
Here, i am not sure how to exactly mock session.request since that is available through the session variable. I tried patching requests.session.request but that does not work here.
Does anyone have any idea how can this function be mocked?
thanks!
I think the reason in #mock.patch(...). You set autospec=True, but your mock_request doesn't return data(in your case everytime - Mock). The documentation says:
If you set autospec=True then the mock will be created with a spec
from the object being replaced. All attributes of the mock will also
have the spec of the corresponding attribute of the object being
replaced. ...
Try to call:
print(mock_request.return_value) # <MagicMock name='request()()' id='4343989392'>
# or
# print(mock_request.return_value.return_value) # <MagicMock name='request()()()' id='4344191568'>
As you can see requests.request was 'mocked' but doesn't return any data. You can set expected data using return_value or side_effect. Here an example:
from unittest import TestCase
import mock
from common import request_url
class MyTestExample(TestCase):
def test_request_url_1(self):
mocked_request = mock.patch('requests.request', side_effect=['one', 'two', 'tree', ])
mocked_request.start()
# request_url(...) will return item from list
self.assertEqual('one', request_url('test', 'url'))
self.assertEqual('two', request_url('test', 'url'))
self.assertEqual('tree', request_url('test', 'url'))
mocked_request.stop()
def test_request_url_2(self):
result = {'my': 'dict'}
mocked_request = mock.patch('requests.request', return_value=result)
mocked_request.start()
# request_url(...) will return static data
self.assertEqual(result, request_url('test', 'url'))
self.assertEqual(result, request_url('test', 'url'))
self.assertEqual(result, request_url('test', 'url'))
mocked_request.stop()
So, you need just describe expected data. Also you can mock API using httpretty.
Hope this helps.

assert JSON response

i have an python API call, and the server response is coming to me as JSON output.
How can I assert that the "status" from this output is 0 for example:
def test_case_connection():
req = requests.get_simple(url=Server.MY_SERVER, params=my_vars)
assert req["status"]="0"
is what i've tried.
The response is looking like:
{"status" : 0, ......}
error i got is:
TypeError: 'Response' object has no attribute '__getitem__'
If you simply need to check that the request was successful, using request.status_code will do the trick:
def test_case_connection():
req = requests.get_simple(url=Server.MY_SERVER, params=my_vars)
assert req.status_code == 200
If you want instead to check for the presence of a specific key-value pair in the response, you need to convert your response payload from json to a dict:
import json
def test_case_connection():
req = requests.get_simple(url=Server.MY_SERVER, params=my_vars)
data = json.loads(req.content)
assert data["status"] == "0"
If you are using the Requests library you can avoid converting json manually by using its builtin json decoder.
It should be assert req['status'] == 0, i. e. comparison (==) instead of assignment (=) and 0 as integer not "0" as string (not entirely sure about the latter).
status code in assertion:
response.ok #it is True if response status code is 200.
In context with pytest it would be like that:
#pytest.mark.parametrize("fixture_name", [(path, json)], indirect=True)
def test_the_response_status_code_first(fixture_name):
assert fixture_name.ok, "The message for the case if the status code != 200."
# the same with checking status code directly:
assert fixture_name.status_code == 200, "Some text for failure. Optional."

How can I mock requests and the response?

I am trying to use Pythons mock package to mock Pythons requests module. What are the basic calls to get me working in below scenario?
In my views.py, I have a function that makes variety of requests.get() calls with different response each time
def myview(request):
res1 = requests.get('aurl')
res2 = request.get('burl')
res3 = request.get('curl')
In my test class I want to do something like this but cannot figure out exact method calls
Step 1:
# Mock the requests module
# when mockedRequests.get('aurl') is called then return 'a response'
# when mockedRequests.get('burl') is called then return 'b response'
# when mockedRequests.get('curl') is called then return 'c response'
Step 2:
Call my view
Step 3:
verify response contains 'a response', 'b response' , 'c response'
How can I complete Step 1 (mocking the requests module)?
This is how you can do it (you can run this file as-is):
import requests
import unittest
from unittest import mock
# This is the class we want to test
class MyGreatClass:
def fetch_json(self, url):
response = requests.get(url)
return response.json()
# This method will be used by the mock to replace requests.get
def mocked_requests_get(*args, **kwargs):
class MockResponse:
def __init__(self, json_data, status_code):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
if args[0] == 'http://someurl.com/test.json':
return MockResponse({"key1": "value1"}, 200)
elif args[0] == 'http://someotherurl.com/anothertest.json':
return MockResponse({"key2": "value2"}, 200)
return MockResponse(None, 404)
# Our test case class
class MyGreatClassTestCase(unittest.TestCase):
# We patch 'requests.get' with our own method. The mock object is passed in to our test case method.
#mock.patch('requests.get', side_effect=mocked_requests_get)
def test_fetch(self, mock_get):
# Assert requests.get calls
mgc = MyGreatClass()
json_data = mgc.fetch_json('http://someurl.com/test.json')
self.assertEqual(json_data, {"key1": "value1"})
json_data = mgc.fetch_json('http://someotherurl.com/anothertest.json')
self.assertEqual(json_data, {"key2": "value2"})
json_data = mgc.fetch_json('http://nonexistenturl.com/cantfindme.json')
self.assertIsNone(json_data)
# We can even assert that our mocked method was called with the right parameters
self.assertIn(mock.call('http://someurl.com/test.json'), mock_get.call_args_list)
self.assertIn(mock.call('http://someotherurl.com/anothertest.json'), mock_get.call_args_list)
self.assertEqual(len(mock_get.call_args_list), 3)
if __name__ == '__main__':
unittest.main()
Important Note: If your MyGreatClass class lives in a different package, say my.great.package, you have to mock my.great.package.requests.get instead of just 'request.get'. In that case your test case would look like this:
import unittest
from unittest import mock
from my.great.package import MyGreatClass
# This method will be used by the mock to replace requests.get
def mocked_requests_get(*args, **kwargs):
# Same as above
class MyGreatClassTestCase(unittest.TestCase):
# Now we must patch 'my.great.package.requests.get'
#mock.patch('my.great.package.requests.get', side_effect=mocked_requests_get)
def test_fetch(self, mock_get):
# Same as above
if __name__ == '__main__':
unittest.main()
Enjoy!
Try using the responses library. Here is an example from their documentation:
import responses
import requests
#responses.activate
def test_simple():
responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
json={'error': 'not found'}, status=404)
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.json() == {"error": "not found"}
assert len(responses.calls) == 1
assert responses.calls[0].request.url == 'http://twitter.com/api/1/foobar'
assert responses.calls[0].response.text == '{"error": "not found"}'
It provides quite a nice convenience over setting up all the mocking yourself.
There's also HTTPretty:
It's not specific to requests library, more powerful in some ways though I found it doesn't lend itself so well to inspecting the requests that it intercepted, which responses does quite easily
There's also httmock.
A new library gaining popularity recently over the venerable requests is httpx, which adds first-class support for async. A mocking library for httpx is: https://github.com/lundberg/respx
Here is what worked for me:
import mock
#mock.patch('requests.get', mock.Mock(side_effect = lambda k:{'aurl': 'a response', 'burl' : 'b response'}.get(k, 'unhandled request %s'%k)))
I used requests-mock for writing tests for separate module:
# module.py
import requests
class A():
def get_response(self, url):
response = requests.get(url)
return response.text
And the tests:
# tests.py
import requests_mock
import unittest
from module import A
class TestAPI(unittest.TestCase):
#requests_mock.mock()
def test_get_response(self, m):
a = A()
m.get('http://aurl.com', text='a response')
self.assertEqual(a.get_response('http://aurl.com'), 'a response')
m.get('http://burl.com', text='b response')
self.assertEqual(a.get_response('http://burl.com'), 'b response')
m.get('http://curl.com', text='c response')
self.assertEqual(a.get_response('http://curl.com'), 'c response')
if __name__ == '__main__':
unittest.main()
this is how you mock requests.post, change it to your http method
#patch.object(requests, 'post')
def your_test_method(self, mockpost):
mockresponse = Mock()
mockpost.return_value = mockresponse
mockresponse.text = 'mock return'
#call your target method now
Here is a solution with requests Response class. It is cleaner IMHO.
import json
from unittest.mock import patch
from requests.models import Response
def mocked_requests_get(*args, **kwargs):
response_content = None
request_url = kwargs.get('url', None)
if request_url == 'aurl':
response_content = json.dumps('a response')
elif request_url == 'burl':
response_content = json.dumps('b response')
elif request_url == 'curl':
response_content = json.dumps('c response')
response = Response()
response.status_code = 200
response._content = str.encode(response_content)
return response
#mock.patch('requests.get', side_effect=mocked_requests_get)
def test_fetch(self, mock_get):
response = requests.get(url='aurl')
assert ...
If you want to mock a fake response, another way to do it is to simply instantiate an instance of the base HttpResponse class, like so:
from django.http.response import HttpResponseBase
self.fake_response = HttpResponseBase()
I started out with Johannes Farhenkrug's answer here and it worked great for me. I needed to mock the requests library because my goal is to isolate my application and not test any 3rd party resources.
Then I read up some more about python's Mock library and I realized that I can replace the MockResponse class, which you might call a 'Test Double' or a 'Fake', with a python Mock class.
The advantage of doing so is access to things like assert_called_with, call_args and so on. No extra libraries are needed. Additional benefits such as 'readability' or 'its more pythonic' are subjective, so they may or may not play a role for you.
Here is my version, updated with using python's Mock instead of a test double:
import json
import requests
from unittest import mock
# defube stubs
AUTH_TOKEN = '{"prop": "value"}'
LIST_OF_WIDGETS = '{"widgets": ["widget1", "widget2"]}'
PURCHASED_WIDGETS = '{"widgets": ["purchased_widget"]}'
# exception class when an unknown URL is mocked
class MockNotSupported(Exception):
pass
# factory method that cranks out the Mocks
def mock_requests_factory(response_stub: str, status_code: int = 200):
return mock.Mock(**{
'json.return_value': json.loads(response_stub),
'text.return_value': response_stub,
'status_code': status_code,
'ok': status_code == 200
})
# side effect mock function
def mock_requests_post(*args, **kwargs):
if args[0].endswith('/api/v1/get_auth_token'):
return mock_requests_factory(AUTH_TOKEN)
elif args[0].endswith('/api/v1/get_widgets'):
return mock_requests_factory(LIST_OF_WIDGETS)
elif args[0].endswith('/api/v1/purchased_widgets'):
return mock_requests_factory(PURCHASED_WIDGETS)
raise MockNotSupported
# patch requests.post and run tests
with mock.patch('requests.post') as requests_post_mock:
requests_post_mock.side_effect = mock_requests_post
response = requests.post('https://myserver/api/v1/get_widgets')
assert response.ok is True
assert response.status_code == 200
assert 'widgets' in response.json()
# now I can also do this
requests_post_mock.assert_called_with('https://myserver/api/v1/get_widgets')
Repl.it links:
https://repl.it/#abkonsta/Using-unittestMock-for-requestspost#main.py
https://repl.it/#abkonsta/Using-test-double-for-requestspost#main.py
This worked for me, although I haven't done much complicated testing yet.
import json
from requests import Response
class MockResponse(Response):
def __init__(self,
url='http://example.com',
headers={'Content-Type':'text/html; charset=UTF-8'},
status_code=200,
reason = 'Success',
_content = 'Some html goes here',
json_ = None,
encoding='UTF-8'
):
self.url = url
self.headers = headers
if json_ and headers['Content-Type'] == 'application/json':
self._content = json.dumps(json_).encode(encoding)
else:
self._content = _content.encode(encoding)
self.status_code = status_code
self.reason = reason
self.encoding = encoding
Then you can create responses :
mock_response = MockResponse(
headers={'Content-Type' :'application/json'},
status_code=401,
json_={'success': False},
reason='Unauthorized'
)
mock_response.raise_for_status()
gives
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: http://example.com
One possible way to work around requests is using the library betamax, it records all requests and after that if you make a request in the same url with the same parameters the betamax will use the recorded request, I have been using it to test web crawler and it save me a lot time.
import os
import requests
from betamax import Betamax
from betamax_serializers import pretty_json
WORKERS_DIR = os.path.dirname(os.path.abspath(__file__))
CASSETTES_DIR = os.path.join(WORKERS_DIR, u'resources', u'cassettes')
MATCH_REQUESTS_ON = [u'method', u'uri', u'path', u'query']
Betamax.register_serializer(pretty_json.PrettyJSONSerializer)
with Betamax.configure() as config:
config.cassette_library_dir = CASSETTES_DIR
config.default_cassette_options[u'serialize_with'] = u'prettyjson'
config.default_cassette_options[u'match_requests_on'] = MATCH_REQUESTS_ON
config.default_cassette_options[u'preserve_exact_body_bytes'] = True
class WorkerCertidaoTRT2:
session = requests.session()
def make_request(self, input_json):
with Betamax(self.session) as vcr:
vcr.use_cassette(u'google')
response = session.get('http://www.google.com')
https://betamax.readthedocs.io/en/latest/
Can you use requests-mock instead?
Suppose your myview function instead takes a requests.Session object, makes requests with it, and does something to the output:
# mypackage.py
def myview(session):
res1 = session.get("http://aurl")
res2 = session.get("http://burl")
res3 = session.get("http://curl")
return f"{res1.text}, {res2.text}, {res3.text}"
# test_myview.py
from mypackage import myview
import requests
def test_myview(requests_mock):
# set up requests
a_req = requests_mock.get("http://aurl", text="a response")
b_req = requests_mock.get("http://burl", text="b response")
c_req = requests_mock.get("http://curl", text="c response")
# test myview behaviour
session = requests.Session()
assert myview(session) == "a response, b response, c response"
# check that requests weren't called repeatedly
assert a_req.called_once
assert b_req.called_once
assert c_req.called_once
assert requests_mock.call_count == 3
You can also use requests_mock with frameworks other than Pytest - the documentation is great.
Using requests_mock is easy to patch any requests
pip install requests-mock
from unittest import TestCase
import requests_mock
from <yourmodule> import <method> (auth)
class TestApi(TestCase):
#requests_mock.Mocker()
def test_01_authentication(self, m):
"""Successful authentication using username password"""
token = 'token'
m.post(f'http://localhost/auth', json= {'token': token})
act_token =auth("user", "pass")
self.assertEqual(act_token, token)
I will add this information since I had a hard time figuring how to mock an async api call.
Here is what I did to mock an async call.
Here is the function I wanted to test
async def get_user_info(headers, payload):
return await httpx.AsyncClient().post(URI, json=payload, headers=headers)
You still need the MockResponse class
class MockResponse:
def __init__(self, json_data, status_code):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
You add the MockResponseAsync class
class MockResponseAsync:
def __init__(self, json_data, status_code):
self.response = MockResponse(json_data, status_code)
async def getResponse(self):
return self.response
Here is the test. The important thing here is I create the response before since init function can't be async and the call to getResponse is async so it all checked out.
#pytest.mark.asyncio
#patch('httpx.AsyncClient')
async def test_get_user_info_valid(self, mock_post):
"""test_get_user_info_valid"""
# Given
token_bd = "abc"
username = "bob"
payload = {
'USERNAME': username,
'DBNAME': 'TEST'
}
headers = {
'Authorization': 'Bearer ' + token_bd,
'Content-Type': 'application/json'
}
async_response = MockResponseAsync("", 200)
mock_post.return_value.post.return_value = async_response.getResponse()
# When
await api_bd.get_user_info(headers, payload)
# Then
mock_post.return_value.post.assert_called_once_with(
URI, json=payload, headers=headers)
If you have a better way of doing that tell me but I think it's pretty clean like that.
The simplest way so far:
from unittest import TestCase
from unittest.mock import Mock, patch
from .utils import method_foo
class TestFoo(TestCase):
#patch.object(utils_requests, "post") # change to desired method here
def test_foo(self, mock_requests_post):
# EXPLANATION: mocked 'post' method above will return some built-in mock,
# and its method 'json' will return mock 'mock_data',
# which got argument 'return_value' with our data to be returned
mock_data = Mock(return_value=[{"id": 1}, {"id": 2}])
mock_requests_post.return_value.json = mock_data
method_foo()
# TODO: asserts here
"""
Example of method that you can test in utils.py
"""
def method_foo():
response = requests.post("http://example.com")
records = response.json()
for record in records:
print(record.get("id"))
# do other stuff here
For those, who don't want to install additional libs for pytest, there is an example. I will duplicate it here with some extension, based on examples above:
import datetime
import requests
class MockResponse:
def __init__(self, json_data, status_code):
self.json_data = json_data
self.status_code = status_code
self.elapsed = datetime.timedelta(seconds=1)
# mock json() method always returns a specific testing dictionary
def json(self):
return self.json_data
def test_get_json(monkeypatch):
# Any arguments may be passed and mock_get() will always return our
# mocked object, which only has the .json() method.
def mock_get(*args, **kwargs):
return MockResponse({'mock_key': 'mock_value'}, 418)
# apply the monkeypatch for requests.get to mock_get
monkeypatch.setattr(requests, 'get', mock_get)
# app.get_json, which contains requests.get, uses the monkeypatch
response = requests.get('https://fakeurl')
response_json = response.json()
assert response_json['mock_key'] == 'mock_value'
assert response.status_code == 418
assert response.elapsed.total_seconds() == 1
============================= test session starts ==============================
collecting ... collected 1 item
test_so.py::test_get_json PASSED [100%]
============================== 1 passed in 0.07s ===============================
To avoid installing other dependencies you should create a fake response. This FakeResponse could be a child of Response (I think this is a good approach because it's more realistic) or just a simple class with the attributes you need.
Simple Fake class
class FakeResponse:
status_code = None
def __init__(self, *args, **kwargs):
self.status_code = 500
self.text = ""
Child of Response
class FakeResponse(Response):
encoding = False
_content = None
def __init__(*args, **kwargs):
super(FakeResponse).__thisclass__.status_code = 500
# Requests requires to be not be None, if not throws an exception
# For reference: https://github.com/psf/requests/issues/3698#issuecomment-261115119
super(FakeResponse).__thisclass__.raw = io.BytesIO()
Just a helpful hint to those that are still struggling, converting from urllib or urllib2/urllib3 to requests AND trying to mock a response- I was getting a slightly confusing error when implementing my mock:
with requests.get(path, auth=HTTPBasicAuth('user', 'pass'), verify=False) as url:
AttributeError: __enter__
Well, of course, if I knew anything about how with works (I didn't), I'd know it was a vestigial, unnecessary context (from PEP 343). Unnecessary when using the requests library because it does basically the same thing for you under the hood. Just remove the with and use bare requests.get(...) and Bob's your uncle.
For pytest users there is a convinient fixture from https://pypi.org/project/pytest-responsemock/
For example to mock GET to http://some.domain you can:
def test_me(response_mock):
with response_mock('GET http://some.domain -> 200 :Nice'):
response = send_request()
assert result.ok
assert result.content == b'Nice'
I will demonstrate how to detach your programming logic from the actual external library by swapping the real request with a fake one that returns the same data. In your view if external api call then this process is best
import pytest
from unittest.mock import patch
from django.test import RequestFactory
#patch("path(projectname.appname.filename).requests.post")
def test_mock_response(self, mock_get, rf: RequestFactory):
mock_get.return_value.ok = Mock(ok=True)
mock_get.return_value.status_code = 400
mock_get.return_value.json.return_value = {you can define here dummy response}
request = rf.post("test/", data=self.payload)
response = view_name_view(request)
expected_response = {
"success": False,
"status": "unsuccessful",
}
assert response.data == expected_response
assert response.status_code == 400
If using pytest:
>>> import pytest
>>> import requests
>>> def test_url(requests_mock):
... requests_mock.get('http://test.com', text='data')
... assert 'data' == requests.get('http://test.com').text
Taken from the official documentation

Categories