Anas Tiour
Anas Tiour

Reputation: 1432

Mocking external API for testing with Python

Context

I am trying to write tests for functions that query an external API. These functions send requests to the API, get responses and process them. In my tests, I want to simulate the external API with a Mock server that is ran locally. So far, the mock server is ran successfully and responds to custom GET queries.

The problem

The external API responds with objects of type <class 'dict'>, while apparently all I can get from my mock server is a response of type <class 'bytes'>. The mock server is fetching pre-defined data from disk and returning them through a stream. Since I can not simulate the external API, my tests throw error messages because of the wrong types of responses.

Following are snippets of my code with some explanations.

1. setUp() function:

The setUp function is ran at the beginning of the test suite. It is responsible of configuring and running the server before running the tests:

def setUp(self):
    self.factory = APIRequestFactory()
    # Configuring the mock server
    self.mock_server_port = get_free_port()
    self.mock_server = HTTPServer(('localhost', self.mock_server_port), MockServerRequestHandler)
    # Run the mock server in a separate thread
    self.mock_server_thread = Thread(target=self.mock_server.serve_forever)
    self.mock_server_thread.setDaemon(True)
    self.mock_server_thread.start()

2. The MockServerClassHandler:

class MockServerRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
    if re.search(config.SYSTEM_STATUS_PATTERN, self.path):
        # Response status code
        self.send_response(requests.codes.ok)
        # Response headers
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.end_headers()
        # Purge response from a file and serve it
        with open('/path/to/my-json-formatted-file') as data_file:
            response_content = json.dumps(json.load(data_file))

        # Writing to a stream that need bytes-like input
        # https://docs.python.org/2/library/basehttpserver.html
        self.wfile.write(response_content.encode('utf-8'))
        return

To my understanding from the official documentation, a BaseHTTPRequestHandler can only serve the content of his response through writing in a predefined stream (wfile) which needs to be given (and I am quoting an error message) a byte-like variable.

So my questions are:

Upvotes: 3

Views: 14368

Answers (1)

Don Kirkby
Don Kirkby

Reputation: 56230

In the comments, it sounds like you solved your main problem, but you're interested in learning how to mock out the web requests instead of launching a dummy web server.

Here's a tutorial on mocking web API requests, and the gory details are in the documentation. If you're using legacy Python, you can install the mock module as a separate package from PyPI.

Here's a snippet from the tutorial:

@patch('project.services.requests.get')
def test_getting_todos_when_response_is_ok(mock_get):
    todos = [{
        'userId': 1,
        'id': 1,
        'title': 'Make the bed',
        'completed': False
    }]

    # Configure the mock to return a response with an OK status code. Also, the mock should have
    # a `json()` method that returns a list of todos.
    mock_get.return_value = Mock(ok=True)
    mock_get.return_value.json.return_value = todos

    # Call the service, which will send a request to the server.
    response = get_todos()

    # If the request is sent successfully, then I expect a response to be returned.
    assert_list_equal(response.json(), todos)

Upvotes: 5

Related Questions