183 lines
5.3 KiB
ReStructuredText
183 lines
5.3 KiB
ReStructuredText
.. _introduction:
|
|
|
|
`Github <https://github.com/gabrielfalcao/HTTPretty>`_
|
|
|
|
|
|
What is HTTPretty ?
|
|
###################
|
|
|
|
.. highlight:: python
|
|
|
|
Once upon a time a python developer wanted to use a RESTful api,
|
|
everything was fine but until the day he needed to test the code that
|
|
hits the RESTful API: what if the API server is down? What if its
|
|
content has changed ?
|
|
|
|
Don't worry, HTTPretty is here for you:
|
|
|
|
::
|
|
|
|
import logging
|
|
import requests
|
|
import httpretty
|
|
|
|
from sure import expect
|
|
|
|
logging.getLogger('httpretty.core').setLevel(logging.DEBUG)
|
|
|
|
@httpretty.activate(allow_net_connect=False)
|
|
def test_yipit_api_returning_deals():
|
|
httpretty.register_uri(httpretty.GET, "http://api.yipit.com/v1/deals/",
|
|
body='[{"title": "Test Deal"}]',
|
|
content_type="application/json")
|
|
|
|
response = requests.get('http://api.yipit.com/v1/deals/')
|
|
|
|
expect(response.json()).to.equal([{"title": "Test Deal"}])
|
|
|
|
|
|
A more technical description
|
|
============================
|
|
|
|
HTTPretty is a python library that swaps the modules :py:mod:`socket`
|
|
and :py:mod:`ssl` with fake implementations that intercept HTTP
|
|
requests at the level of a TCP connection.
|
|
|
|
It is inspired on Ruby's `FakeWeb <http://fakeweb.rubyforge.org/>`_.
|
|
|
|
If you come from the Ruby programming language this would probably sound familiar :smiley:
|
|
|
|
Installing
|
|
==========
|
|
|
|
Installing httpretty is as easy as:
|
|
|
|
.. highlight:: bash
|
|
|
|
::
|
|
|
|
pip install httpretty
|
|
|
|
|
|
Demo
|
|
####
|
|
|
|
expecting a simple response body
|
|
================================
|
|
|
|
|
|
.. code:: python
|
|
|
|
import requests
|
|
import httpretty
|
|
|
|
def test_one():
|
|
httpretty.enable(verbose=True, allow_net_connect=False) # enable HTTPretty so that it will monkey patch the socket module
|
|
httpretty.register_uri(httpretty.GET, "http://yipit.com/",
|
|
body="Find the best daily deals")
|
|
|
|
response = requests.get('http://yipit.com')
|
|
|
|
assert response.text == "Find the best daily deals"
|
|
|
|
httpretty.disable() # disable afterwards, so that you will have no problems in code that uses that socket module
|
|
httpretty.reset() # reset HTTPretty state (clean up registered urls and request history)
|
|
|
|
|
|
making assertions in a callback that generates the response body
|
|
================================================================
|
|
|
|
.. code:: python
|
|
|
|
import requests
|
|
import json
|
|
import httpretty
|
|
|
|
@httpretty.activate
|
|
def test_with_callback_response():
|
|
def request_callback(request, uri, response_headers):
|
|
content_type = request.headers.get('Content-Type')
|
|
assert request.body == '{"nothing": "here"}', 'unexpected body: {}'.format(request.body)
|
|
assert content_type == 'application/json', 'expected application/json but received Content-Type: {}'.format(content_type)
|
|
return [200, response_headers, json.dumps({"hello": "world"})]
|
|
|
|
httpretty.register_uri(
|
|
httpretty.POST, "https://httpretty.example.com/api",
|
|
body=request_callback)
|
|
|
|
response = requests.post('https://httpretty.example.com/api', headers={'Content-Type': 'application/json'}, data='{"nothing": "here"}')
|
|
|
|
expect(response.json()).to.equal({"hello": "world"})
|
|
|
|
|
|
Link headers
|
|
============
|
|
|
|
|
|
Tests link headers by using the `adding_headers` parameter.
|
|
|
|
|
|
.. code:: python
|
|
|
|
import requests
|
|
from sure import expect
|
|
import httpretty
|
|
|
|
|
|
@httpretty.activate
|
|
def test_link_response():
|
|
first_url = "http://foo-api.com/data"
|
|
second_url = "http://foo-api.com/data?page=2"
|
|
link_str = "<%s>; rel='next'" % second_url
|
|
|
|
httpretty.register_uri(
|
|
httpretty.GET,
|
|
first_url,
|
|
body='{"success": true}',
|
|
status=200,
|
|
content_type="text/json",
|
|
adding_headers={"Link": link_str},
|
|
)
|
|
httpretty.register_uri(
|
|
httpretty.GET,
|
|
second_url,
|
|
body='{"success": false}',
|
|
status=500,
|
|
content_type="text/json",
|
|
)
|
|
# Performs a request to `first_url` followed by some testing
|
|
response = requests.get(first_url)
|
|
expect(response.json()).to.equal({"success": True})
|
|
expect(response.status_code).to.equal(200)
|
|
next_url = response.links["next"]["url"]
|
|
expect(next_url).to.equal(second_url)
|
|
|
|
# Follow the next URL and perform some testing.
|
|
response2 = requests.get(next_url)
|
|
expect(response2.json()).to.equal({"success": False})
|
|
expect(response2.status_code).to.equal(500)
|
|
|
|
|
|
Motivation
|
|
##########
|
|
|
|
When building systems that access external resources such as RESTful
|
|
webservices, XMLRPC or even simple HTTP requests, we stumble in the
|
|
problem:
|
|
|
|
*"I'm gonna need to mock all those requests"*
|
|
|
|
It can be a bit of a hassle to use something like
|
|
:py:class:`mock.Mock` to stub the requests, this can work well for
|
|
low-level unit tests but when writing functional or integration tests
|
|
we should be able to allow the http calls to go through the TCP socket
|
|
module.
|
|
|
|
HTTPretty `monkey patches
|
|
<http://en.wikipedia.org/wiki/Monkey_patch>`_ Python's
|
|
:py:mod:`socket` core module with a fake version of the module.
|
|
|
|
Because HTTPretty implements a fake the modules :py:mod:`socket` and
|
|
:py:mod:`ssl` you can use write tests to code against any HTTP library
|
|
that use those modules.
|