Fixture manager for httpretty
Project description
Fixture manager for httpretty
Features:
Reuse responses across tests
Allows maintaining state between requests
Access past request information
On per-fixture basis
Across all fixtures
This was written to solve communicating to an Elasticsearch during tests. For our usage, mock didn’t scale well and placing httpretty fixtures on our base test case was impratical. To solve this, we wrote a fixture manager, httpretty-fixtures.
Getting Started
Install the module with: pip install httpretty_fixtures
# Load in our dependencies
import json
import unittest
import httpretty_fixtures
import requests
# Set up our fixture manager
class FakeElasticsearch(httpretty_fixtures.FixtureManager):
@httpretty_fixtures.get('http://localhost:9200/my_index/my_document/my_id')
def es_index(self, request, uri, res_headers):
return (200, res_headers, json.dumps({
'_index': 'my_index',
'_type': 'my_document',
'_id': 'my_id',
'_version': 1,
'found': True,
}))
# Define our tests
class MyTestCase(unittest.TestCase):
@FakeElasticsearch.run(['es_index'])
def test_retrieve_from_es(self, fake_elasticsearch):
"""Verify we can retrieve an item from Elasticsearch"""
# Make our request and verify we hit Elasticsearch
res = requests.get('http://localhost:9200/my_index/my_document/my_id?first')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.json()['_index'], 'my_index')
# Make a second request for demonstration purposes
requests.get('http://localhost:9200/my_index/my_document/my_id?second')
# Introspect our request received on `FakeElasticsearch`
fixture = fake_elasticsearch.es_index
self.assertEqual(fixture.first_request.path, '/my_index/my_document/my_id?first')
self.assertEqual(fixture.last_request.path, '/my_index/my_document/my_id?second')
self.assertEqual(len(fixture.requests), 2)
self.assertEqual(fixture.requests[0].path, '/my_index/my_document/my_id?first')
self.assertEqual(fixture.requests[1].path, '/my_index/my_document/my_id?second')
# Access request information from all `httpretty` requests
self.assertEqual(httpretty_fixtures.first_request().path, '/my_index/my_document/my_id?first')
self.assertEqual(httpretty_fixtures.last_request().path, '/my_index/my_document/my_id?second')
self.assertEqual(len(httpretty_fixtures.requests()), 2)
self.assertEqual(httpretty_fixtures.requests()[0].path, '/my_index/my_document/my_id?first')
self.assertEqual(httpretty_fixtures.requests()[1].path, '/my_index/my_document/my_id?second')
Documentation
httpretty-fixtures exports FixtureManager, get, put, post, delete, head, patch, options, connect, first_request, last_request, and requests as methods/variables.
We will refer to the package as httpretty_fixtures.
FixtureManager()
Class for setting up a set of fixtures on. This should be inherited from into another class with its own set of fixtures.
class FakeElasticsearch(httpretty_fixtures.FixtureManager):
@httpretty_fixtures.get('http://localhost:9200/my_index/my_document/my_id')
def es_index(self, request, uri, res_headers):
return (200, res_headers, json.dumps({'content': 'goes here'}))
fixture_manager.run(fixtures)
Decorator to run a set of fixtures during a function
fixtures list - Names of fixture functions to run
* str - Name of fixtures function to run
We will pass in the server instance as an argument to the decorated function. From the server, we can access per-instance fixture information and requests.
class FakeElasticsearch(httpretty_fixtures.FixtureManager):
@httpretty_fixtures.get('http://localhost:9200/my_index/my_document/my_id')
def es_index(self, request, uri, res_headers):
return (200, res_headers, json.dumps({}))
class MyTestCase(unittest.TestCase):
# The `es_index` fixture will be live for all of this test case
@FakeElasticsearch.run(['es_index'])
def test_retrieve_from_es(self, fake_elasticsearch):
"""Verify we can retrieve an item from Elasticsearch"""
# Make our request and verify we hit Elasticsearch
res = requests.get('http://localhost:9200/my_index/my_document/my_id')
fixture_manager.start(fixtures)
Start running HTTPretty with a set of fixtures
fixtures list - Names of fixture functions to run
* str - Name of fixtures function to run
Returns:
Returns a running instance of fixture_manager. This can be used to access fixtures and their request information.
.start() will run HTTPretty indefinitely until .stop() is called.
fixture_manager.stop()
Stop a running instance of HTTPretty. This should always be run at some point after a .start()
httpretty_fixtures.{verb}(*register_uri_args, **register_uri_kwargs)
Decorator to register a fixture function under an HTTP verb
This is a summary for all possible HTTP verbs:
@httpretty_fixtures.get()
@httpretty_fixtures.put()
@httpretty_fixtures.post()
@httpretty_fixtures.delete()
@httpretty_fixtures.head()
@httpretty_fixtures.patch()
@httpretty_fixtures.options()
@httpretty_fixtures.connect()
Each of these verbs functions passes its arguments/keyword arguments to HTTPretty's register_uri` function.
If there are any arguments you want to apply to your fixture with respect to HTTPretty, this is how to do it.
https://github.com/gabrielfalcao/HTTPretty/tree/0.8.3#usage
@httpretty_fixtures.get("http://underdog.io/")
Function signature
httpretty_fixtures leverages the dynamic callback functionality of httpretty:
https://github.com/gabrielfalcao/HTTPretty/tree/0.8.3#dynamic-responses-through-callbacks
As a result, we expect our decorator to receive a function that matches the following signature:
@httpretty_fixtures.get("http://underdog.io/")
def request_handler(self, request, uri, res_headers):
res_tuple = (status_code, res_headers, body)
return res_tuple
# Example
@httpretty_fixtures.get("http://underdog.io/")
def hello(self, request, uri, res_headers):
return (200, res_headers, 'Hello World!')
The signature is as follows:
request_handler function - Handler for our request callback
self object - Instance of class extended on top of for FixtureManager
uri object - Information about incoming request
Structure is managed by httpretty
More info can be read from the source code
res_headers object - Default response headers to provide to request
These should be modified and/or passed through in the res_tuple
res_tuple tuple - Collection of information for our response
[0] int - Status code to provide for response
For example, 200 would be a 200 HTTP status code
[1] object - Modified or provided set of headers provided as a parameter
[2] str - Response body for our request
In the example above, we replied with 'Hello World!' but this could be JSON, XML, or whatever you need
Function attributes
httpretty_fixtures provides helper properties to access past request information. For the sake of reference, we will refer to a fixture as fixture
fixture.first_request - Accesses first request received by fixture in our .run() current instance
If no request was received, then this will be None
fixture.last_request - Accesses last request received by fixture in our .run() current instance
If no request was received, then this will be None
fixture.requests - List of all requests received by our fixture
A fixture should be accessible via the returned server from our .run() decorator or .start()
class MyTestCase(unittest.TestCase):
# The `es_index` fixture will be live for all of this test case
@FakeElasticsearch.run(['es_index'])
def test_retrieve_from_es(self, fake_elasticsearch):
# Access our `fixture` and its properties
fake_elasticsearch.es_index
fake_elasticsearch.es_index.first_request
fake_elasticsearch.es_index.last_request
fake_elasticsearch.es_index.requests
httpretty_fixtures.first_request()
Alias to access the first request received by HTTPretty.
Warning: If you are using HTTPretty in other locations, then this will register those requests as well.
httpretty_fixtures.last_request()
Alias to access the last request received by HTTPretty.
Warning: If you are using HTTPretty in other locations, then this will register those requests as well.
httpretty_fixtures.requests()
Alias to access all requests received by HTTPretty.
Warning: If you are using HTTPretty in other locations, then this will register those requests as well.
Examples
Preserving state between requests
In this example, we will count between multiple requests to indicate that state is being preserved.
# Load in our dependencies
import unittest
import httpretty_fixtures
import requests
# Set up our fixture manager
class CounterServer(httpretty_fixtures.FixtureManager):
def __init__(self):
self.count = 0
super(CounterServer, self).__init__()
@httpretty_fixtures.get('http://localhost:9000/')
def counter(self, request, uri, res_headers):
self.count += 1
return (200, res_headers, str(self.count))
# Define our tests
class MyTestCase(unittest.TestCase):
@CounterServer.run(['counter'])
def test_counter_state(self, counter_server):
"""Verify we can preserve state between requests"""
# Make our first request and verify its count
res = requests.get('http://localhost:9000/')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.text, '1')
# Make our second request and verify its count
res = requests.get('http://localhost:9000/')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.text, '2')
@CounterServer.run(['counter'])
def test_counter_alternate_state(self, counter_server):
"""Verify state is not maintained between separate `FixtureManager.run()'s`"""
res = requests.get('http://localhost:9000/')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.text, '1')
Contributing
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Test via nosetests.
License
Copyright (c) 2015 Underdog.io
Licensed under the MIT license.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file httpretty_fixtures-2.1.1.tar.gz
.
File metadata
- Download URL: httpretty_fixtures-2.1.1.tar.gz
- Upload date:
- Size: 10.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | e027d361eb73e202d42909b23a04e0725a222145783371b33f4abdce55208173 |
|
MD5 | 8d7969a2e4b3d4f581cd67337dc5d362 |
|
BLAKE2b-256 | f6b582b95bad0ccddbe006ad9fddabccb5feedd5cf889d56fd6b865aaf8afd40 |