At Points, we are creating an architecture that uses REST services extensively. We needed to find a way to mock HTTP responses to our REST service calls so that we could run our Python applications in isolation for development and testing purposes.

Our solution was to mock our responses using HTTPretty in a client wrapper.

What HTTPretty is

HTTPretty allows you to mock a response for a specified request while using your favourite HTTP client library. It does this by monkey patching the socket module. This means that any library can be used to make the request, and your mocked response will be returned. We use the Requests library.

How HTTPretty works

Using it is simple.

For each HTTP request you want to mock, you must activate HTTPretty. Next, register the URL you wish to intercept. The  URL and method must be specified, and the headers, body, and status code of the expected response may all be mocked.

import httpretty
import requests
@httpretty.activate
def call_remote_service():
    url = 'http://myservice.points.com/members/1'
     httpretty.register_uri(httpretty.GET, url
     body='{"firstName": "Steve"}')
     response = requests.get(url)
     return response

What we did

For each remote service that we call from our applications, we created both a client and a test client wrapper. The client is where we make the actual HTTP request to the remote service. The client wrapper activates HTTPretty, primes it with the expected response, and then calls the real client.

client_wrapper.py
@httpretty.activate
import httpretty
import real_client

@httpretty.activate
def call_remote_service(url):
    """ Wrapper for real client. """
    httpretty.register_uri(httpretty.GET, url,
                           body='{"firstName": "Steve"}',
                           content_type='application/json; charset=utf-8')
    response = real_client.call_remote_service(url)
    return response
 real_client.py
import requests

def call_remote_service(url):
    """ Real remote http call """
    response = requests.get(url)
    return response

When the wrapper is used, the HTTP request that would normally be made by the requests library in real_client.py is intercepted at the socket layer by HTTPretty.

If we are running the application locally or otherwise testing in isolation, we use client_wrapper to access the services. Otherwise, we use real_client directly. In our Django web applications, we specify the appropriate client in our settings files, and then dynamically import it into the calling module.

settings.py
SERVICE_URL = 'http://myservice.points.com/members/{}'
RS_CLIENT = 'myapp.mocks.rs_client'
test.py
import httplib

from django import test
from django.utils import importlib

def import_module_from_settings(setting):
    """ Import a module given a path specifying path and module name.
        Uses Django utilities. """
    # Lifted & modified from Django code
    i = setting.rfind('.')
    package, module = setting[:i], setting[i:]
    return importlib.import_module(module, package)

def test_call_client_wrapper(self):
        client = import_module_from_settings(settings.RS_CLIENT)
        full_path = full_path = settings.SERVICE_URL.format('5')

        response = client.call_remote_service(full_path)
        response_dict = response.json()

        self.assertEqual(httplib.OK, response.status_code)
        self.assertEqual('Steve', response_dict['firstName'])

Fake services as an alternative

At Points, we have traditionally used fake services when we need to run our webapps in isolation.  The service url is configured to point to a resource on the local server when running  web tests or a local development server. The local service then processes the request. This approach works and is reliable, but requires you to code a fake service.

We also examined some tools which allow you to run external fake services and create test responses. Although these free you from actually implementing the service, we were wary of the overhead required to install, configure, and maintain them.

Why we did it

We decided to go with mock responses instead of a fake service. The advantages we identified include:

  • Seamless local integration: allows us to run our app locally and in our continuous integration environment without having to hit real services

  • Reliability: not dependant on running and configuring an external fake service

  • Little overhead: necessary code and configuration are minimal

  • Control: completely in control of the expected responses

Have any questions? Post them below!

Shares 0