diff --git a/docs/index.rst b/docs/index.rst index 5664707..d998eec 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -121,6 +121,23 @@ a special ``json`` attribute appended to the ``Response`` object:: response = self.client.get("/ajax/") self.assertEquals(response.json, dict(success=True)) + +Single app instance +------------------- + +In some cases, creating the app instance can be expensive. If you want to create one +app instance per TestCase class, you can use the ``create_app_once`` flag. This works +on both TestCase and LiveServerTestCase and is not enabled by default:: + + from flask_testing import TestCase + + class MyTest(TestCase): + create_app_once = True + + def test_something(self): + pass + + Opt to not render the templates ------------------------------- diff --git a/flask_testing/utils.py b/flask_testing/utils.py index a01b5ef..30350ac 100644 --- a/flask_testing/utils.py +++ b/flask_testing/utils.py @@ -57,7 +57,7 @@ except ImportError: # pragma: no cover _is_signals = False -__all__ = ["TestCase"] +__all__ = ('TestCase', 'LiveServerTestCase') class ContextVariableDoesNotExist(Exception): @@ -110,7 +110,28 @@ def _check_for_signals_support(): ) -class TestCase(unittest.TestCase): +class SingletonAppMixin(object): + create_app_once = False + _app_instance = None + + def _get_or_create_app(self): + """ + This is a lazy way of doing class setup since we want to be consistent + and not have users call super in setUpClass if they do not call it in + setUp. Returns the singleton app if the test case has specified using + a single app. + """ + cls = self.__class__ + if not cls.create_app_once: + return self.create_app() + + if not cls._app_instance: + cls._app_instance = self.create_app() + + return cls._app_instance + + +class TestCase(unittest.TestCase, SingletonAppMixin): render_templates = True run_gc_after_test = False @@ -141,7 +162,7 @@ def debug(self): self._post_teardown() def _pre_setup(self): - self.app = self.create_app() + self.app = self._get_or_create_app() self._orig_response_class = self.app.response_class self.app.response_class = _make_test_response(self.app.response_class) @@ -410,8 +431,8 @@ def assert500(self, response, message=None): # A LiveServerTestCase useful with Selenium or headless browsers # Inspired by https://docs.djangoproject.com/en/dev/topics/testing/#django.test.LiveServerTestCase +class LiveServerTestCase(unittest.TestCase, SingletonAppMixin): -class LiveServerTestCase(unittest.TestCase): def create_app(self): """ Create your Flask app here, with any @@ -426,7 +447,7 @@ def __call__(self, result=None): """ # Get the app - self.app = self.create_app() + self.app = self._get_or_create_app() self._configured_port = self.app.config.get('LIVESERVER_PORT', 5000) self._port_value = multiprocessing.Value('i', self._configured_port) diff --git a/tests/__init__.py b/tests/__init__.py index a9296af..2161512 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -4,18 +4,20 @@ from .test_twill import TestTwill, TestTwillDeprecated from .test_utils import TestSetup, TestSetupFailure, TestClientUtils, \ - TestLiveServer, TestTeardownGraceful, TestRenderTemplates, \ - TestNotRenderTemplates, TestRestoreTheRealRender, \ - TestLiveServerOSPicksPort + TestLiveServer, TestTeardownGraceful, TestRenderTemplates, \ + TestNotRenderTemplates, TestRestoreTheRealRender, TestSingletonApp, \ + TestLiveServerOSPicksPort, TestLiveServerSingletonApp def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestSetup)) suite.addTest(unittest.makeSuite(TestSetupFailure)) + suite.addTest(unittest.makeSuite(TestSingletonApp)) suite.addTest(unittest.makeSuite(TestClientUtils)) suite.addTest(unittest.makeSuite(TestLiveServer)) suite.addTest(unittest.makeSuite(TestLiveServerOSPicksPort)) + suite.addTest(unittest.makeSuite(TestLiveServerSingletonApp)) suite.addTest(unittest.makeSuite(TestTeardownGraceful)) suite.addTest(unittest.makeSuite(TestRenderTemplates)) suite.addTest(unittest.makeSuite(TestNotRenderTemplates)) diff --git a/tests/test_utils.py b/tests/test_utils.py index f785634..a40ceb9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -40,6 +40,22 @@ def test_remove_testcase_attributes(self): del self.app + +class TestSingletonApp(TestCase): + create_app_once = True + apps_created = 0 + + def create_app(self): + self.__class__.apps_created += 1 + return create_app() + + def test_create_app_once(self): + self.assertEqual(self.apps_created, 1) + + def test_create_app_once_sanity(self): + # In case sorting method changes + self.assertEqual(self.apps_created, 1) + class TestClientUtils(TestCase): def create_app(self): @@ -245,6 +261,22 @@ def create_app(self): return app +class TestLiveServerSingletonApp(BaseTestLiveServer): + create_app_once = True + apps_created = 0 + + def create_app(self): + self.__class__.apps_created += 1 + return create_app() + + def test_created_single_app(self): + self.assertEqual(1, self.apps_created) + + def test_created_single_app_sanity(self): + # In case they run out of order + self.assertEqual(1, self.apps_created) + + class TestNotRenderTemplates(TestCase): render_templates = False