# Testing Helpers

The `treq.testing` module provides some tools for testing both HTTP clients which use the treq API and implementations of the Twisted Web resource model.

## Writing tests for HTTP clients

The `StubTreq` class implements the `treq` module interface (`treq.get()`, `treq.post()`, etc.) but runs all I/O via a `MemoryReactor`. It wraps a `twisted.web.resource.IResource` provider which handles each request.

You can wrap a pre-existing IResource provider, or write your own. For example, the `twisted.web.resource.ErrorPage` resource can produce an arbitrary HTTP status code. `twisted.web.static.File` can serve files or directories. And you can easily achieve custom responses by writing trivial resources yourself:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10

    @implementer(IResource)
    class JsonResource(object):
        isLeaf = True  # NB: means getChildWithDefault will not be called

        def __init__(self, data):
            self.data = data

        def render(self, request):
            request.setHeader(b'Content-Type', b'application/json')
            return json.dumps(self.data).encode('utf-8')

However, those resources don’t assert anything about the request. The `RequestSequence` and `StringStubbingResource` classes make it easy to construct a resource which encodes the expected request and response pairs. Do note that most parameters to these functions must be bytes—it’s safest to use the `b''` string syntax, which works on both Python 2 and 3.

For example:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56

    from twisted.internet import defer
    from twisted.trial.unittest import SynchronousTestCase
    from twisted.web import http

    from treq.testing import StubTreq, HasHeaders
    from treq.testing import RequestSequence, StringStubbingResource

    @defer.inlineCallbacks
    def make_a_request(treq):
        """
        Make a request using treq.
        """
        response = yield treq.get('http://an.example/foo', params={'a': 'b'},
                                  headers={b'Accept': b'application/json'})
        if response.code == http.OK:
            result = yield response.json()
        else:
            message = yield response.text()
            raise Exception("Got an error from the server: {}".format(message))
        defer.returnValue(result)

    class MakeARequestTests(SynchronousTestCase):
        """
        Test :func:`make_a_request()` using :mod:`treq.testing.RequestSequence`.
        """

        def test_200_ok(self):
            """On a 200 response, return the response's JSON."""
            req_seq = RequestSequence([
                ((b'get', 'http://an.example/foo', {b'a': [b'b']},
                  HasHeaders({'Accept': ['application/json']}), b''),
                 (http.OK, {b'Content-Type': b'application/json'}, b'{"status": "ok"}'))
            ])
            treq = StubTreq(StringStubbingResource(req_seq))

            with req_seq.consume(self.fail):
                result = self.successResultOf(make_a_request(treq))

            self.assertEqual({"status": "ok"}, result)

        def test_418_teapot(self):
            """On an unexpected response code, raise an exception"""
            req_seq = RequestSequence([
                ((b'get', 'http://an.example/foo', {b'a': [b'b']},
                  HasHeaders({'Accept': ['application/json']}), b''),
                 (418, {b'Content-Type': b'text/plain'}, b"I'm a teapot!"))
            ])
            treq = StubTreq(StringStubbingResource(req_seq))

            with req_seq.consume(self.fail):
                failure = self.failureResultOf(make_a_request(treq))

            self.assertEqual(u"Got an error from the server: I'm a teapot!",
                             failure.getErrorMessage())

This may be run with `trial testing_seq.py`. Download: `testing_seq.py`.

### Loosely matching the request

If you don’t care about certain parts of the request, you can pass `mock.ANY`, which compares equal to anything. This sequence matches a single GET request with any parameters or headers:

    RequestSequence([
        ((b'get', mock.ANY, mock.ANY, b''), (200, {}, b'ok'))
    ])

If you care about headers, use `HasHeaders` to make assertions about the headers present in the request. It compares equal to a superset of the headers specified, which helps make your test robust to changes in treq or Agent. Right now treq adds the `Accept-Encoding: gzip` header, but as support for additional compression methods is added, this may change.

## Writing tests for Twisted Web resources

Since `StubTreq` wraps any resource, you can use it to test your server-side code as well. This is superior to calling your resource’s methods directly or passing mock objects, since it uses a real `Agent` to generate the request and a real `Site` to process the response. Thus, the `request` object your code interacts with is a real `twisted.web.server.Request` and behaves the same as it would in production.

Note that if your resource returns `NOT_DONE_YET` you must keep a reference to the `RequestTraversalAgent` and call its `flush()` method to spin the memory reactor once the server writes additional data before the client will receive it.