Summary
In a prior post, we covered how to write a mock API server that stored a thread as a class attribute and used it to run the server in the background by starting a thread.
However, we neglected to cover how to actually use the mock API server. So here we include some examples of how you can use the mock API server to write better tests for components that require interacting with APIs.
The MockAPIServer Class
Let's start with a recap of the mock API server class. Major features included:
-
Inheriting from the base HTTP server class in Python, to take advantage of the methods available through it
-
Using a singleton design pattern to start and stop the fake API server
Basically we create the server, call start_serving()
, and that starts the
server on a thread in the background.
Here is the source code:
class MockAPIServer(BaseHTTPRequestHandler):
_server = None
_thread = None
@staticmethod
def get_addr_port():
addr = "127.0.0.1"
port = "9876"
return addr, port
@classmethod
def start_serving(cls):
# Get the bind address and port
cls._addr, cls._port = cls.get_addr_port()
# Create an HTTP server
cls._server = HTTPServer((cls._addr, cls._port), cls)
# Create a thread to run the server
cls._thread = threading.Thread(target=cls._server.serve_forever)
# Start the server
cls._thread.start()
@classmethod
def stop_serving(cls):
# Shut down the server
if cls._server is not None:
cls._server.shutdown()
# Let the thread rejoin the worker pool
cls._thread.join(timeout=10)
assert not cls._thread.is_alive()
def do_POST(self):
ctype, pdict = cgi.parse_header(self.headers.get("content-type"))
# Enforce rule: JSON only
if ctype != "application/json":
self.send_response(400)
self.end_headers()
return
# Convert received JSON to dict
length = int(self.headers.get("content-length"))
message = json.loads(self.rfile.read(length))
# Process the json
# Send a response
response = bytes(json.dumps(message), "utf8")
self._set_headers()
self.wfile.write(response)
def _set_headers(self):
self.send_response(200)
self.send_header("Content-type", "application/json")
self.end_headers()
A Basic Unit Test with MockAPIServer
Let's make a basic test that uses the MockAPIServer
class. We'll use
unittest
for simplicity, other testing frameworks offer similar
functionality.
Before testing our code, we'll need to make sure the API URL is configurable, sicne we will need to get the mock API server's bind address and port and use those to instruct our code where to find the API server.
Here is a short example function that we'll test:
foobar.py
:
import urllib.parse
def make_api_call(api_url)
"""
A simple function that gets an API endpoint
and returns if no problems raised.
"""
# Assemble our API call
endpoint = '/hello/world'
url = urllib.parse.urljoin(api_url, endpoint)
params = dict(message='hello world')
# The basic mock server will just echo our request back
data = requests.get(url, params=params)
data = resp.json()
return
Now we write a short test for our foobar script:
test_foobar.py
:
from foobar import make_api_call
import unittest
class TestAPICalls(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.app = MockAPIServer()
cls.app.start_serving()
def test_api_call():
addr, port = self.app
api_url = urllib.parse.urljoin(
'http://', addr, port
)
make_api_cal(api_url)
@classmethod
def tearDownClass(cls):
cls.app.stop_serving()
We can run the test like so:
python test_foobar.py
Here's what happens when we run the test:
- The
setUpClass()
method is called, which creates a mock API server that starts on localhost on port 9876 and runs the server on a thread. - The
test_api_call()
method is run, which makes the API call to the mock API server. (Nothing interesting is happening on either end, right now, but stay tuned for more examples.) - The
tearDownClass()
method is called, which stops the mock API server and returns the thread worker to the pool of workers.
Stay tuned for more complicated examples in the future - we are currently working on extending this mock API server to mock calls to the Github API.