From 9689d6d4524c166125c2901bfb3c7ffbb22aaafd Mon Sep 17 00:00:00 2001 From: Jonathan Como Date: Mon, 27 Nov 2017 13:11:10 -0500 Subject: [PATCH 1/4] All option for LiveServerTestCase to create single app --- flask_testing/utils.py | 24 ++++++++++++++++++++++-- tests/__init__.py | 3 ++- tests/test_utils.py | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/flask_testing/utils.py b/flask_testing/utils.py index a01b5ef..7ed074b 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): @@ -412,6 +412,9 @@ def assert500(self, response, message=None): # Inspired by https://docs.djangoproject.com/en/dev/topics/testing/#django.test.LiveServerTestCase class LiveServerTestCase(unittest.TestCase): + create_app_once = False + _app_instance = None + def create_app(self): """ Create your Flask app here, with any @@ -426,7 +429,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) @@ -443,6 +446,23 @@ def get_server_url(self): """ return 'http://localhost:%s' % self._port_value.value + 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 (also could not do this in a class method since create_app + is not a classmethod). + """ + 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 + def _spawn_live_server(self): self._process = None port_value = self._port_value diff --git a/tests/__init__.py b/tests/__init__.py index a9296af..af3f79a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,7 +6,7 @@ from .test_utils import TestSetup, TestSetupFailure, TestClientUtils, \ TestLiveServer, TestTeardownGraceful, TestRenderTemplates, \ TestNotRenderTemplates, TestRestoreTheRealRender, \ - TestLiveServerOSPicksPort + TestLiveServerOSPicksPort, TestLiveServerReuseApp def suite(): @@ -16,6 +16,7 @@ def suite(): suite.addTest(unittest.makeSuite(TestClientUtils)) suite.addTest(unittest.makeSuite(TestLiveServer)) suite.addTest(unittest.makeSuite(TestLiveServerOSPicksPort)) + suite.addTest(unittest.makeSuite(TestLiveServerReuseApp)) 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..15be23a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -245,6 +245,23 @@ def create_app(self): return app +class TestLiveServerReuseApp(BaseTestLiveServer): + create_app_once = True + apps_created = 0 + + def create_app(self): + self.__class__.apps_created += 1 + app = create_app() + return 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 From 269520ea7e866026b00bf05e21056e99d465df92 Mon Sep 17 00:00:00 2001 From: Jonathan Como Date: Mon, 27 Nov 2017 13:26:04 -0500 Subject: [PATCH 2/4] Use mixin to allow singleton behavior in regular TestCase --- flask_testing/utils.py | 48 ++++++++++++++++++++++-------------------- tests/__init__.py | 9 ++++---- tests/test_utils.py | 21 +++++++++++++++--- 3 files changed, 48 insertions(+), 30 deletions(-) diff --git a/flask_testing/utils.py b/flask_testing/utils.py index 7ed074b..d9a9356 100644 --- a/flask_testing/utils.py +++ b/flask_testing/utils.py @@ -110,7 +110,29 @@ 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 (also could not do this in a class method since create_app + is not a classmethod). + """ + 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 +163,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,10 +432,7 @@ 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): - create_app_once = False - _app_instance = None +class LiveServerTestCase(unittest.TestCase, SingletonAppMixin): def create_app(self): """ @@ -446,23 +465,6 @@ def get_server_url(self): """ return 'http://localhost:%s' % self._port_value.value - 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 (also could not do this in a class method since create_app - is not a classmethod). - """ - 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 - def _spawn_live_server(self): self._process = None port_value = self._port_value diff --git a/tests/__init__.py b/tests/__init__.py index af3f79a..2161512 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -4,19 +4,20 @@ from .test_twill import TestTwill, TestTwillDeprecated from .test_utils import TestSetup, TestSetupFailure, TestClientUtils, \ - TestLiveServer, TestTeardownGraceful, TestRenderTemplates, \ - TestNotRenderTemplates, TestRestoreTheRealRender, \ - TestLiveServerOSPicksPort, TestLiveServerReuseApp + 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(TestLiveServerReuseApp)) + 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 15be23a..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,14 +261,13 @@ def create_app(self): return app -class TestLiveServerReuseApp(BaseTestLiveServer): +class TestLiveServerSingletonApp(BaseTestLiveServer): create_app_once = True apps_created = 0 def create_app(self): self.__class__.apps_created += 1 - app = create_app() - return app + return create_app() def test_created_single_app(self): self.assertEqual(1, self.apps_created) From 5878bbd7d4625b8c2e9d13f5ab9a3fb55d35c935 Mon Sep 17 00:00:00 2001 From: Jonathan Como Date: Mon, 27 Nov 2017 13:31:05 -0500 Subject: [PATCH 3/4] Add docs for new feature --- docs/index.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 ------------------------------- From 1afa3bbdf7ca71941f2ec1d1385acbf6948f8e97 Mon Sep 17 00:00:00 2001 From: Jonathan Como Date: Mon, 27 Nov 2017 13:34:07 -0500 Subject: [PATCH 4/4] Remove extra comment --- flask_testing/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flask_testing/utils.py b/flask_testing/utils.py index d9a9356..30350ac 100644 --- a/flask_testing/utils.py +++ b/flask_testing/utils.py @@ -119,8 +119,7 @@ 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 (also could not do this in a class method since create_app - is not a classmethod). + a single app. """ cls = self.__class__ if not cls.create_app_once: