From 3d746c64eb4aac50e84f15480839214ca1c19593 Mon Sep 17 00:00:00 2001 From: Kurt Griffiths Date: Tue, 12 Feb 2013 14:57:42 -0500 Subject: [PATCH] refactor(testing): Factor out classes into their own modules.\n\nFixes #75 --- falcon/testing/__init__.py | 10 ++- falcon/testing/helpers.py | 151 ++++---------------------------- falcon/testing/srmock.py | 48 ++++++++++ falcon/testing/test_resource.py | 74 ++++++++++++++++ falcon/testing/test_suite.py | 70 +++++++++++++++ 5 files changed, 216 insertions(+), 137 deletions(-) create mode 100644 falcon/testing/srmock.py create mode 100644 falcon/testing/test_resource.py create mode 100644 falcon/testing/test_suite.py diff --git a/falcon/testing/__init__.py b/falcon/testing/__init__.py index 94bf171..694bb04 100644 --- a/falcon/testing/__init__.py +++ b/falcon/testing/__init__.py @@ -1,5 +1,4 @@ -"""Test Suite and helper functions for unit testing API's -implemented on top of Falcon. +"""Helper classes and functions for unit-testing API's implemented on Falcon. Copyright 2013 by Rackspace Hosting, Inc. @@ -17,5 +16,8 @@ limitations under the License. """ -# Hoist classes and functions into the falcon namespace -from falcon.testing.helpers import * +# Hoist classes and functions into the falcon.testing namespace +from falcon.testing.helpers import * # NOQA +from falcon.testing.srmock import StartResponseMock # NOQA +from falcon.testing.test_resource import TestResource # NOQA +from falcon.testing.test_suite import TestSuite # NOQA \ No newline at end of file diff --git a/falcon/testing/helpers.py b/falcon/testing/helpers.py index a195ee2..7a54a37 100644 --- a/falcon/testing/helpers.py +++ b/falcon/testing/helpers.py @@ -1,9 +1,26 @@ +"""Defines helper functions for unit testing. + +Copyright 2013 by Rackspace Hosting, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +""" + import random import io import sys from datetime import datetime -import testtools import six import falcon @@ -36,138 +53,6 @@ def rand_string(min, max): for i in range(string_length)]) -class StartResponseMock: - """Mock object that represents a WSGI start_response callable - - Attributes: - call_count: Number of times start_response was called. - status: HTTP status line, e.g. "785 TPS Cover Sheet not attached". - headers: Headers array passed to start_response, per PEP-333 - headers_dict: Headers array parsed into a dict to facilitate lookups - - """ - - def __init__(self): - """Initialize attributes to default values""" - - self._called = 0 - self.status = None - self.headers = None - - def __call__(self, status, headers): - """Implements the PEP-333 start_response protocol""" - - self._called += 1 - self.status = status - self.headers = headers - self.headers_dict = dict(headers) - - @property - def call_count(self): - return self._called - - -class TestResource: - """Falcon test resource. - - Implements on_get only, and captures request data, as well as - sets resp body and some sample headers. - - Attributes: - sample_status: HTTP status set on the response - sample_body: Random body string set on the response - resp_headers: Sample headers set on the response - - req: Request passed into the on_get responder - resp: Response passed into the on_get responder - kwargs: Keyword arguments (URI fields) passed into the on_get responder - called: True if on_get was ever called; False otherwise - - - """ - - sample_status = "200 OK" - sample_body = rand_string(0, 128 * 1024) - resp_headers = { - 'Content-Type': 'text/plain; charset=utf-8', - 'ETag': '10d4555ebeb53b30adf724ca198b32a2', - 'X-Hello': 'OH HAI' - } - - def __init__(self): - """Initializes called to False""" - - self.called = False - - def on_get(self, req, resp, **kwargs): - """GET responder - - Captures req, resp, and kwargs. Also sets up a sample response. - - Args: - req: Falcon Request instance - resp: Falcon Response instance - kwargs: URI template name=value pairs - - """ - - # Don't try this at home - classes aren't recreated - # for every request - self.req, self.resp, self.kwargs = req, resp, kwargs - - self.called = True - resp.status = falcon.HTTP_200 - resp.body = self.sample_body - resp.set_headers(self.resp_headers) - - -class TestSuite(testtools.TestCase): - """ Creates a basic TestSuite for testing an API endpoint. - - Inherit from this and write your test methods. If the child class defines - a prepare(self) method, this method will be called before executing each - test method. - - Attributes: - api: falcon.API instance used in simulating requests. - srmock: falcon.testing.StartResponseMock instance used in - simulating requests. - test_route: Randomly-generated route string (path) that tests can - use when wiring up resources. - - - """ - - def setUp(self): - """Initializer, unittest-style""" - - super(TestSuite, self).setUp() - self.api = falcon.API() - self.srmock = StartResponseMock() - self.test_route = '/' + self.getUniqueString() - - prepare = getattr(self, 'prepare', None) - if hasattr(prepare, '__call__'): - prepare() - - def simulate_request(self, path, **kwargs): - """ Simulates a request. - - Simulates a request to the API for testing purposes. - - Args: - path: Request path for the desired resource - kwargs: Same as falcon.testing.create_environ() - - """ - - if not path: - path = '/' - - return self.api(create_environ(path=path, **kwargs), - self.srmock) - - def create_environ(path='/', query_string='', protocol='HTTP/1.1', port='80', headers=None, app='', body='', method='GET', wsgierrors=None): diff --git a/falcon/testing/srmock.py b/falcon/testing/srmock.py new file mode 100644 index 0000000..cb05c49 --- /dev/null +++ b/falcon/testing/srmock.py @@ -0,0 +1,48 @@ +"""Defines the StartResponseMock class. + +Copyright 2013 by Rackspace Hosting, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +""" + + +class StartResponseMock: + """Mock object that represents a WSGI start_response callable + + Attributes: + call_count: Number of times start_response was called. + status: HTTP status line, e.g. "785 TPS Cover Sheet not attached". + headers: Headers array passed to start_response, per PEP-333 + headers_dict: Headers array parsed into a dict to facilitate lookups + + """ + + def __init__(self): + """Initialize attributes to default values""" + + self._called = 0 + self.status = None + self.headers = None + + def __call__(self, status, headers): + """Implements the PEP-333 start_response protocol""" + + self._called += 1 + self.status = status + self.headers = headers + self.headers_dict = dict(headers) + + @property + def call_count(self): + return self._called diff --git a/falcon/testing/test_resource.py b/falcon/testing/test_resource.py new file mode 100644 index 0000000..64171e3 --- /dev/null +++ b/falcon/testing/test_resource.py @@ -0,0 +1,74 @@ +"""Defines the TestResource class. + +Copyright 2013 by Rackspace Hosting, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +""" + +from falcon import HTTP_200 +from .helpers import rand_string + + +class TestResource: + """Falcon test resource. + + Implements on_get only, and captures request data, as well as + sets resp body and some sample headers. + + Attributes: + sample_status: HTTP status set on the response + sample_body: Random body string set on the response + resp_headers: Sample headers set on the response + + req: Request passed into the on_get responder + resp: Response passed into the on_get responder + kwargs: Keyword arguments (URI fields) passed into the on_get responder + called: True if on_get was ever called; False otherwise + + + """ + + sample_status = "200 OK" + sample_body = rand_string(0, 128 * 1024) + resp_headers = { + 'Content-Type': 'text/plain; charset=utf-8', + 'ETag': '10d4555ebeb53b30adf724ca198b32a2', + 'X-Hello': 'OH HAI' + } + + def __init__(self): + """Initializes called to False""" + + self.called = False + + def on_get(self, req, resp, **kwargs): + """GET responder + + Captures req, resp, and kwargs. Also sets up a sample response. + + Args: + req: Falcon Request instance + resp: Falcon Response instance + kwargs: URI template name=value pairs + + """ + + # Don't try this at home - classes aren't recreated + # for every request + self.req, self.resp, self.kwargs = req, resp, kwargs + + self.called = True + resp.status = HTTP_200 + resp.body = self.sample_body + resp.set_headers(self.resp_headers) diff --git a/falcon/testing/test_suite.py b/falcon/testing/test_suite.py new file mode 100644 index 0000000..d691a64 --- /dev/null +++ b/falcon/testing/test_suite.py @@ -0,0 +1,70 @@ +"""Defines the TestSuite class. + +Copyright 2013 by Rackspace Hosting, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +""" + +import testtools + +import falcon +from .srmock import StartResponseMock +from .helpers import create_environ + + +class TestSuite(testtools.TestCase): + """Scaffolding around testtools.TestCase for testing a Falcon API endpoint. + + Inherit from this and write your test methods. If the child class defines + a prepare(self) method, this method will be called before executing each + test method. + + Attributes: + api: falcon.API instance used in simulating requests. + srmock: falcon.testing.StartResponseMock instance used in + simulating requests. + test_route: Randomly-generated route string (path) that tests can + use when wiring up resources. + + + """ + + def setUp(self): + """Initializer, unittest-style""" + + super(TestSuite, self).setUp() + self.api = falcon.API() + self.srmock = StartResponseMock() + self.test_route = '/' + self.getUniqueString() + + prepare = getattr(self, 'prepare', None) + if hasattr(prepare, '__call__'): + prepare() + + def simulate_request(self, path, **kwargs): + """ Simulates a request. + + Simulates a request to the API for testing purposes. + + Args: + path: Request path for the desired resource + kwargs: Same as falcon.testing.create_environ() + + """ + + if not path: + path = '/' + + return self.api(create_environ(path=path, **kwargs), + self.srmock)