Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: How to unit test a custom HTTP request Handler?

I have a custom HTTP request handler that can be simplified to something like this:

# Python 3:
from http import server

class MyHandler(server.BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()

        # Here's where all the complicated logic is done to generate HTML.
        # For clarity here, replace with a simple stand-in:
        html = "<html><p>hello world</p></html>"

        self.wfile.write(html.encode())

I'd like to unit-test this handler (i.e. make sure that my do_GET executes without an exception) without actually starting a web server. Is there any lightweight way to mock the SimpleHTTPServer so that I can test this code?

like image 859
jakevdp Avatar asked Aug 18 '14 17:08

jakevdp


Video Answer


2 Answers

Expanding on the answer from jakevdp, I managed to be able to check the output, too:

try:
    import unittest2 as unittest
except ImportError:
    import unittest
try:
    from io import BytesIO as IO
except ImportError:
    from StringIO import StringIO as IO
from server import MyHandlerSSL  # My BaseHTTPRequestHandler child


class TestableHandler(MyHandlerSSL):
    # On Python3, in socketserver.StreamRequestHandler, if this is
    # set it will use makefile() to produce the output stream. Otherwise,
    # it will use socketserver._SocketWriter, and we won't be able to get
    # to the data
    wbufsize = 1

    def finish(self):
        # Do not close self.wfile, so we can read its value
        self.wfile.flush()
        self.rfile.close()

    def date_time_string(self, timestamp=None):
        """ Mocked date time string """
        return 'DATETIME'

    def version_string(self):
        """ mock the server id """
        return 'BaseHTTP/x.x Python/x.x.x'


class MockSocket(object):
    def getsockname(self):
        return ('sockname',)


class MockRequest(object):
    _sock = MockSocket()

    def __init__(self, path):
        self._path = path

    def makefile(self, *args, **kwargs):
        if args[0] == 'rb':
            return IO(b"GET %s HTTP/1.0" % self._path)
        elif args[0] == 'wb':
            return IO(b'')
        else:
            raise ValueError("Unknown file type to make", args, kwargs)


class HTTPRequestHandlerTestCase(unittest.TestCase):
    maxDiff = None

    def _test(self, request):
        handler = TestableHandler(request, (0, 0), None)
        return handler.wfile.getvalue()

    def test_unauthenticated(self):
        self.assertEqual(
                self._test(MockRequest(b'/')),
                b"""HTTP/1.0 401 Unauthorized\r
Server: BaseHTTP/x.x Python/x.x.x\r
Date: DATETIME\r
WWW-Authenticate: Basic realm="MyRealm", charset="UTF-8"\r
Content-type: text/html\r
\r
<html><head><title>Authentication Failed</title></html><body><h1>Authentication Failed</h1><p>Authentication Failed. Authorised Personnel Only.</p></body></html>"""
                )


def main():
    unittest.main()


if __name__ == "__main__":
    main()

The code I am testing returns a 401 Unauthorised for "/". Change the response as appopriate for your test case.

like image 87
user10610334 Avatar answered Nov 02 '22 19:11

user10610334


So this is a little tricky depending on how "deep" you want to go into the BaseHTTPRequestHandler behavior to define your unit test. At the most basic level I think you can use this example from the mock library:

>>> from mock import MagicMock
>>> thing = ProductionClass()
>>> thing.method = MagicMock(return_value=3)
>>> thing.method(3, 4, 5, key='value')
3
>>> thing.method.assert_called_with(3, 4, 5, key='value')

So if you know which methods in the BaseHTTPRequestHandler your class is going to call you could mock the results of those methods to be something acceptable. This can of course get pretty complex depending on how many different types of server responses you want to test.

like image 20
ACV Avatar answered Nov 02 '22 19:11

ACV