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
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:
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:
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:
Here's what happens when we run the test:
setUpClass()method is called, which creates a mock API server that starts on localhost on port 9876 and runs the server on a thread.
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.)
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.