Skip to content

Commit

Permalink
feat(testing): Add TestClient class and a pytest example
Browse files Browse the repository at this point in the history
Add a contextual wrapper for the simulate_* methods and a pytest
example demonstrating its use.

Also deprecate "api" in favor of "app" in TestCase, since these utils
can work with any WSGI app (not just those that express HTTP APIs). This
terminology makes TestCase more consistent with the new simulate_*
functions and with TestClient.

Finally, deprecate "api_class" in TestCase and update the docstring
for the testing module to demonstrate a less hacky alternative for
controlling the api instance type. As a bonus, this avoids having to
try and alias api_class (a class variable).

Partially implements #16
  • Loading branch information
Kurt Griffiths committed Aug 12, 2016
1 parent 498b4b6 commit 66a6898
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 168 deletions.
3 changes: 2 additions & 1 deletion doc/api/testing.rst
Expand Up @@ -4,9 +4,10 @@ Testing
=======

.. automodule:: falcon.testing
:members: Result, TestCase, SimpleTestResource, StartResponseMock,
:members: Result,
simulate_request, simulate_get, simulate_head, simulate_post,
simulate_put, simulate_options, simulate_patch, simulate_delete,
TestClient, TestCase, SimpleTestResource, StartResponseMock,
capture_responder_args, rand_string, create_environ

Deprecated
Expand Down
48 changes: 38 additions & 10 deletions falcon/testing/__init__.py
Expand Up @@ -16,23 +16,51 @@
This package contains various test classes and utility functions to
support functional testing for both Falcon-based apps and the Falcon
framework itself::
framework itself. Both unittest-style and pytest-style tests are
supported::
# -----------------------------------------------------------------
# unittest-style
# -----------------------------------------------------------------
from falcon import testing
from myapp import app
from myapp import api
class TestMyApp(testing.TestCase):
class MyTestCase(testing.TestCase):
def setUp(self):
super(TestMyApp, self).setUp()
self.api = app.create_api()
super(MyTestCase, self).setUp()
self.app = api.create()
def test_get_message(self):
doc = {u'message': u'Hello world!'}
result = self.simulate_get('/messages/42')
self.assertEqual(result.json, doc)
class TestMyApp(MyTestCase):
def test_get_message(self):
doc = {u'message': u'Hello world!'}
result = self.simulate_get('/messages/42')
self.assertEqual(result.json, doc)
# -----------------------------------------------------------------
# pytest-style
# -----------------------------------------------------------------
from falcon import testing
import pytest
from myapp import api
@pytest.fixture(scope='module')
def client():
return testing.TestClient(api.create())
def test_get_message(client):
doc = {u'message': u'Hello world!'}
For additional examples, see also Falcon's own test suite.
result = client.simulate_get('/messages/42')
assert result.json == doc
"""

# Hoist classes and functions into the falcon.testing namespace
Expand Down
116 changes: 108 additions & 8 deletions falcon/testing/client.py
Expand Up @@ -125,7 +125,9 @@ def simulate_request(app, method='GET', path='/', query_string=None,
headers=None, body=None, file_wrapper=None):
"""Simulates a request to a WSGI application.
Performs a request against a WSGI application callable.
Performs a request against a WSGI application. Uses
:any:`wsgiref.validate` to ensure the response is valid
WSGI.
Keyword Args:
app (callable): The WSGI application to call
Expand Down Expand Up @@ -186,7 +188,9 @@ def simulate_request(app, method='GET', path='/', query_string=None,
def simulate_get(app, path, **kwargs):
"""Simulates a GET request to a WSGI application.
Equivalent to ``simulate_request(app, 'GET', ...)``
Equivalent to::
simulate_request(app, 'GET', path, **kwargs)
Args:
app (callable): The WSGI application to call
Expand All @@ -204,7 +208,9 @@ def simulate_get(app, path, **kwargs):
def simulate_head(app, path, **kwargs):
"""Simulates a HEAD request to a WSGI application.
Equivalent to ``simulate_request(app, 'HEAD', ...)``
Equivalent to::
simulate_request(app, 'HEAD', path, **kwargs)
Args:
app (callable): The WSGI application to call
Expand All @@ -222,7 +228,9 @@ def simulate_head(app, path, **kwargs):
def simulate_post(app, path, **kwargs):
"""Simulates a POST request to a WSGI application.
Equivalent to ``simulate_request(app, 'POST', ...)``
Equivalent to::
simulate_request(app, 'POST', path, **kwargs)
Args:
app (callable): The WSGI application to call
Expand All @@ -244,7 +252,9 @@ def simulate_post(app, path, **kwargs):
def simulate_put(app, path, **kwargs):
"""Simulates a PUT request to a WSGI application.
Equivalent to ``simulate_request(app, 'PUT', ...)``
Equivalent to::
simulate_request(app, 'PUT', path, **kwargs)
Args:
app (callable): The WSGI application to call
Expand All @@ -266,7 +276,9 @@ def simulate_put(app, path, **kwargs):
def simulate_options(app, path, **kwargs):
"""Simulates an OPTIONS request to a WSGI application.
Equivalent to ``simulate_request(app, 'OPTIONS', ...)``
Equivalent to::
simulate_request(app, 'OPTIONS', path, **kwargs)
Args:
app (callable): The WSGI application to call
Expand All @@ -284,7 +296,9 @@ def simulate_options(app, path, **kwargs):
def simulate_patch(app, path, **kwargs):
"""Simulates a PATCH request to a WSGI application.
Equivalent to ``simulate_request(app, 'PATCH', ...)``
Equivalent to::
simulate_request(app, 'PATCH', path, **kwargs)
Args:
app (callable): The WSGI application to call
Expand All @@ -306,7 +320,9 @@ def simulate_patch(app, path, **kwargs):
def simulate_delete(app, path, **kwargs):
"""Simulates a DELETE request to a WSGI application.
Equivalent to ``simulate_request(app, 'DELETE', ...)``
Equivalent to::
simulate_request(app, 'DELETE', path, **kwargs)
Args:
app (callable): The WSGI application to call
Expand All @@ -319,3 +335,87 @@ def simulate_delete(app, path, **kwargs):
(default: ``None``)
"""
return simulate_request(app, 'DELETE', path, **kwargs)


class TestClient(object):
""""Simulates requests to a WSGI application.
This class provides a contextual wrapper for Falcon's simulate_*
test functions. It lets you replace this::
simulate_get(app, '/messages')
simulate_head(app, '/messages')
with this::
client = TestClient(app)
client.simulate_get('/messages')
client.simulate_head('/messages')
Args:
app (callable): A WSGI application to target when simulating
requests
"""

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

def simulate_get(self, path='/', **kwargs):
"""Simulates a GET request to a WSGI application.
See also: :py:meth:`falcon.testing.simulate_get`.
"""
return simulate_get(self.app, path, **kwargs)

def simulate_head(self, path='/', **kwargs):
"""Simulates a HEAD request to a WSGI application.
See also: :py:meth:`falcon.testing.simulate_head`.
"""
return simulate_head(self.app, path, **kwargs)

def simulate_post(self, path='/', **kwargs):
"""Simulates a POST request to a WSGI application.
See also: :py:meth:`falcon.testing.simulate_post`.
"""
return simulate_post(self.app, path, **kwargs)

def simulate_put(self, path='/', **kwargs):
"""Simulates a PUT request to a WSGI application.
See also: :py:meth:`falcon.testing.simulate_put`.
"""
return simulate_put(self.app, path, **kwargs)

def simulate_options(self, path='/', **kwargs):
"""Simulates an OPTIONS request to a WSGI application.
See also: :py:meth:`falcon.testing.simulate_options`.
"""
return simulate_options(self.app, path, **kwargs)

def simulate_patch(self, path='/', **kwargs):
"""Simulates a PATCH request to a WSGI application.
See also: :py:meth:`falcon.testing.simulate_patch`.
"""
return simulate_patch(self.app, path, **kwargs)

def simulate_delete(self, path='/', **kwargs):
"""Simulates a DELETE request to a WSGI application.
See also: :py:meth:`falcon.testing.simulate_delete`.
"""
return simulate_delete(self.app, path, **kwargs)

def simulate_request(self, *args, **kwargs):
"""Simulates a request to a WSGI application.
Wraps :py:meth:`falcon.testing.simulate_request` to perform a
WSGI request directly against ``self.app``. Equivalent to::
falcon.testing.simulate_request(self.app, *args, **kwargs)
"""

return simulate_request(self.app, *args, **kwargs)

0 comments on commit 66a6898

Please sign in to comment.