Testing Python app which does HTTP requests - python

For testing I use pytest so it would be great if you suggest something pytest specific.
I have some code which uses the requests library. What it does is basically simple POST/GET requests for logging in, parsing data, etc.
Surely I want to test that code locally without doing any actual HTTP requests.
A monkeypatch funcarg could be the solution, but I think that mocking request.get(...) calls or directly pythons's urllib isn't good, because, for example, there are functions which do more than one HTTP request inside , so I can't just mock the request.get("anyURL") with a simple lambda *args, **kwaargs: """<html>response</html>""".
There are different URLs which should return different content. Sometimes it should be based on POST/GET data. Also I have no idea how will requests.session behave in case of direct mocking. Besides that how to emulate session termination? How to emulate a connection failure?
So in the end in my opinion it's quite hard to use monkey patching here. At least I am not able to write a good mocking function which will take into account everything. Also if I choose to mock urllib directly and someday requests library starts using something different all my tests will fail.
So the best way I think is to use actual HTTP server which turns on on a test run, and if possible takes into account pytest's scopes, etc (so it's a funcarg). While googling I found only two solutions:
https://pypi.python.org/pypi/pytest-localserver
https://github.com/kevin1024/pytest-httpbin
The first one sets up the HTTP server and serves predefined content over a specific URL. Definitely that does not work for me, because as I mentioned some functions which I intend to test do several requests so all inner HTTP requests.get() will get the same answer. Bad.
The second one as far a I see has the same problem. Or at least do not understand how to use it.
The third option could be writing a small Flask based service, but I guess I'll run into a problem that things I use in tests should be tested as well which is a bad practice.

You can rather unmock get after first call.
class Requester():
def get(*args):
...
def mock_get(requester, response):
orig_get = requester.get
def return_text_and_unmock(*args, **kwargs):
self.get = orig_get
return response
requester.get = return_text_and_unmock.__get__(requester, Requester)
return requester

I believe using a local server for unit testing is not a good idea as this is not really a unit test. I you're using requests one good way of being able to mock the requests is to use the module responses that is developed and maintained by dropbox: response dropbox. With responses you will be able to mock each request you make by specifying that you want a certain content to be return when a request is issued to a given URL. The README gives a quick overview of the module's abilities.

Related

Does vcrpy's record_mode=None guarantee no HTTP requests will be sent even if the requests are made in a thread?

I am testing some code with pytest and using the vcrpy decorator as follows:
#pytest.mark.vcr(record_mode='none')
def test_something():
make_requests_in_a_thread_and_save_to_queue()
logged_responses = log_responses_from_queue_in_a_thread()
assert logged_responses == expected_logged_responses
The test fails because the logged_responses are new responses, which are the results of new HTTP requests that have been made during test_something().
I have a cassette saved in the correct place, but this is probably irrelevant because even if I didn't I should be getting a vcrpy CassetteError rather than a failed test.
Does record_mode='none' not apply to code executed within threads?
If not, how should I approach the testing problem? Thank you!
I found out what the problem was. I was using a stream API rather than sending http requests. record_mode='none' refers to http requests.

How to write a unit test code for functions which make API requests to web?

I am writing unit-test code with pytest for my python functions.
Those functions work with requests module that allows us to easily talk to the web servers.
The thing is that the web server to which the functions communicate don't return same value.
For example, the functions communicate to www.toolmarket.com to ask how much Tommy drill is. But sometimes, Tommy drill is not in the list of the web shop. In this case, My test code returns f.
How can I write test code for this kind of function of code?
Edit : added test code
def test_get_data():
assert Info(None, None, None).get_data("Tommy drill") == (
"id" : "KRG552100411"
)
I want to do test like above. Tommy drill is just one of items in the shop. But the thing is sometimes, the item disappears from the list. So test returns f
The above code snippet looks like unit testing. And, since you are using requests module in the actual code, it is better you MOCK the API calls while writing the unit-tests.
It doesn't make an API call from the test function because we use a python mock. With mock, we use #patch and specify python where we are actually making an API call so that it knows what to mock.
It is validating the code if it can process the response. As a reason why, we don't want to make an API call to an external service every time we do unit testing.
To summarise -
When you make the actual API call, you're not doing unit testing, that's more like integration testing
When you want to do unit testing, you're testing if your code can accept and process the expected API call response without actually making the call. You do this by using a mocking library (for example the 'responses' library, which injects mock responses to calls made by requests.
If this helps, I would be happy to help on Python Mocking of API requests too.

Properly Unit test around a Flask App and Mocking Flask.request.json? [duplicate]

This question already has an answer here:
Testing code that requires a Flask app or request context
(1 answer)
Closed 3 years ago.
I'm attempting to write unit tests for a file that is included inside of a flask app (microservice to handle some RESTful endpoints). Some things I'm running into: All the documentation and questions I seem to find involve invoking the APIs themselves, this isn't proper I need to invoke the post function directly with a mocked fake request that I've setup as I need to test functionality.
I'm able to attempt to mock the request.json object however i'm always met with "RuntimeError: Working outside of request context".I've attempted to use test_request_context() but that leads to the same issue. Then I started diving deeper into Flask and attempting to us app.test_client() however this has it's own problems alongside of calling the endpoint directly and doesn't let me unit test my function properly, they start moving into the realm of being integration tests.
This is the function i'm attempting to test:
#api.route...
class HandleRoute(Resource):
#authentication #<-- I want to Mock True or False response
def post(self): #<-- I want to call directly
try:
if not request.json #<-- Value I want to mock, from flask.request
How should I be going about this? I'm trying to avoid app.test_client() and other Flask related things as again, the point of these unit tests is to check my code paths as a sanity check, not just what should happen.
You are correct in wanting to actually perform the request, and test the received response. This is the proper way and Flask already has a built-in client to make this easy.
The official documentation already includes an example of how to do this (Testing Flask Applications). If you want to have more control over the request being processed, then have a look at Manually push a context.
I believe you are looking for the following snippet (adapted from this answer):
with app.test_client() as client:
resp = client.post('/route', {'some_key': 'some_data'})
If you want to do away with flask.test_client() you can use python-requests to stay in the python world, or even use javascript based libraries like Chakram or Frisby

Send a GET request with a body

I'm using elasticsearch and the RESTful API supports supports reading bodies in GET requests for search criteria.
I'm currently doing
response = urllib.request.urlopen(url, data).read().decode("utf-8")
If data is present, it issues a POST, otherwise a GET. How can I force a GET despite the fact that I'm including data (which should be in the request body as per a POST)
Nb: I'm aware I can use a source property in the Url but the queries we're running are complex and the query definition is quite verbose resulting in extremely long urls (long enough that they can interfere with some older browsers and proxies).
I'm not aware of a nice way to do this using urllib. However, requests makes it trivial (and, in fact, trivial with any arbitrary verb and request content) by using the requests.request* function:
requests.request(method='get', url='localhost/test', data='some data')
Constructing a small test web server will show that the data is indeed sent in the body of the request, and that the method perceived by the server is indeed a GET.
*note that I linked to the requests.api.requests code because that's where the actual function definition lives. You should call it using requests.request(...)

functional testing during development

I am not exposed to many of the testing framework, and wonder any recommendation on achieving the following (functional testing) during development phase. Intention is to test a web application functionality (language agnostic?) though the exposed http (REST/JSON RPC) interface.
My backend in NOT written in Python, but because of the easiness of using requests library, and creating Ad hoc http request, I simply construct http POST/GET request with appropriate cookie, payload etc and check the response to validate the server correctness.
It is little tedious to enable specific test cases (comment out / boolean flag ), and verify the results. Any framework to make this more pleasant during the development phase where frequent changes are the norm.
thanks.
Well your on the right track with requests you could tie that directly into nose or unittest or any of the common python testing frameworks that exist, bit of background requests was actually written for testing flask
Use nose to run your tests. In this case, you can declare base classes of your tests to be like this:
class SlowTestBase(BaseTestCase):
slow = True
And run it like nosetests --attr="slow", or to exclude them --attr="!slow". You can find more on nose documentation at https://nose.readthedocs.org/en/latest/

Categories