Add a TestCase mixin for enabling caches in tests
By default, disable all caching in tests, to preserve test independence. In order to enable caching, inherit from CacheSetupMixin, and specify which cache configuration is needed. [EV-32]
This commit is contained in:
@@ -40,6 +40,8 @@ class BookmarksTestsBase(ModuleStoreTestCase):
|
||||
STORE_TYPE = ModuleStoreEnum.Type.mongo
|
||||
TEST_PASSWORD = 'test'
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
super(BookmarksTestsBase, self).setUp()
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ class CourseStructureApiTests(ModuleStoreTestCase):
|
||||
"""
|
||||
MOCK_CACHE = "openedx.core.djangoapps.content.course_structures.api.v0.api.cache"
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Test setup
|
||||
|
||||
@@ -16,16 +16,20 @@ from openedx.core.djangoapps.credentials.utils import (
|
||||
)
|
||||
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@attr('shard_2')
|
||||
class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin, CredentialsDataMixin,
|
||||
ProgramsDataMixin, TestCase):
|
||||
ProgramsDataMixin, CacheIsolationTestCase):
|
||||
""" Tests covering the retrieval of user credentials from the Credentials
|
||||
service.
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(TestCredentialsRetrieval, self).setUp()
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ class CreditApiTestBase(ModuleStoreTestCase):
|
||||
Base class for test cases of the credit API.
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
PROVIDER_ID = "hogwarts"
|
||||
PROVIDER_NAME = "Hogwarts School of Witchcraft and Wizardry"
|
||||
PROVIDER_URL = "https://credit.example.com/request"
|
||||
|
||||
@@ -31,6 +31,7 @@ class ReverificationPartitionTest(ModuleStoreTestCase):
|
||||
SUBMITTED = "submitted"
|
||||
APPROVED = "approved"
|
||||
DENIED = "denied"
|
||||
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
|
||||
|
||||
def setUp(self):
|
||||
super(ReverificationPartitionTest, self).setUp()
|
||||
|
||||
@@ -17,6 +17,7 @@ from openedx.core.djangoapps.programs import utils
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
from openedx.core.djangoapps.programs.tests import factories
|
||||
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
|
||||
|
||||
@@ -26,8 +27,11 @@ UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
|
||||
@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@attr('shard_2')
|
||||
class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin,
|
||||
CredentialsApiConfigMixin, TestCase):
|
||||
CredentialsApiConfigMixin, CacheIsolationTestCase):
|
||||
"""Tests covering the retrieval of programs from the Programs service."""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(TestProgramRetrieval, self).setUp()
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from student.tests.factories import UserFactory
|
||||
from student.models import UserProfile, LanguageProficiency, PendingEmailChange
|
||||
from openedx.core.djangoapps.user_api.accounts import ACCOUNT_VISIBILITY_PREF_KEY
|
||||
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from .. import PRIVATE_VISIBILITY, ALL_USERS_VISIBILITY
|
||||
|
||||
TEST_PROFILE_IMAGE_UPLOADED_AT = datetime.datetime(2002, 1, 9, 15, 43, 01, tzinfo=UTC)
|
||||
@@ -121,10 +122,13 @@ class UserAPITestCase(APITestCase):
|
||||
clear=True
|
||||
)
|
||||
@attr('shard_2')
|
||||
class TestAccountAPI(UserAPITestCase):
|
||||
class TestAccountAPI(CacheIsolationTestCase, UserAPITestCase):
|
||||
"""
|
||||
Unit tests for the Account API.
|
||||
"""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(TestAccountAPI, self).setUp()
|
||||
|
||||
@@ -244,7 +248,7 @@ class TestAccountAPI(UserAPITestCase):
|
||||
"""
|
||||
self.different_client.login(username=self.different_user.username, password=self.test_password)
|
||||
self.create_mock_profile(self.user)
|
||||
with self.assertNumQueries(11):
|
||||
with self.assertNumQueries(14):
|
||||
response = self.send_get(self.different_client)
|
||||
self._verify_full_shareable_account_response(response, account_privacy=ALL_USERS_VISIBILITY)
|
||||
|
||||
@@ -259,7 +263,7 @@ class TestAccountAPI(UserAPITestCase):
|
||||
"""
|
||||
self.different_client.login(username=self.different_user.username, password=self.test_password)
|
||||
self.create_mock_profile(self.user)
|
||||
with self.assertNumQueries(11):
|
||||
with self.assertNumQueries(14):
|
||||
response = self.send_get(self.different_client)
|
||||
self._verify_private_account_response(response, account_privacy=PRIVATE_VISIBILITY)
|
||||
|
||||
@@ -307,11 +311,11 @@ class TestAccountAPI(UserAPITestCase):
|
||||
Test that a client (logged in) can get her own account information (using default legacy profile information,
|
||||
as created by the test UserFactory).
|
||||
"""
|
||||
def verify_get_own_information():
|
||||
def verify_get_own_information(queries):
|
||||
"""
|
||||
Internal helper to perform the actual assertions
|
||||
"""
|
||||
with self.assertNumQueries(9):
|
||||
with self.assertNumQueries(queries):
|
||||
response = self.send_get(self.client)
|
||||
data = response.data
|
||||
self.assertEqual(17, len(data))
|
||||
@@ -333,12 +337,12 @@ class TestAccountAPI(UserAPITestCase):
|
||||
self.assertEqual(False, data["accomplishments_shared"])
|
||||
|
||||
self.client.login(username=self.user.username, password=self.test_password)
|
||||
verify_get_own_information()
|
||||
verify_get_own_information(12)
|
||||
|
||||
# Now make sure that the user can get the same information, even if not active
|
||||
self.user.is_active = False
|
||||
self.user.save()
|
||||
verify_get_own_information()
|
||||
verify_get_own_information(9)
|
||||
|
||||
def test_get_account_empty_string(self):
|
||||
"""
|
||||
@@ -352,7 +356,7 @@ class TestAccountAPI(UserAPITestCase):
|
||||
legacy_profile.save()
|
||||
|
||||
self.client.login(username=self.user.username, password=self.test_password)
|
||||
with self.assertNumQueries(9):
|
||||
with self.assertNumQueries(12):
|
||||
response = self.send_get(self.client)
|
||||
for empty_field in ("level_of_education", "gender", "country", "bio"):
|
||||
self.assertIsNone(response.data[empty_field])
|
||||
|
||||
@@ -20,6 +20,7 @@ from social.apps.django_app.default.models import UserSocialAuth
|
||||
|
||||
from django_comment_common import models
|
||||
from openedx.core.lib.api.test_utils import ApiTestCase, TEST_API_KEY
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from student.tests.factories import UserFactory
|
||||
from third_party_auth.tests.testutil import simulate_running_pipeline, ThirdPartyAuthTestMixin
|
||||
from third_party_auth.tests.utils import (
|
||||
@@ -334,12 +335,14 @@ class UserViewSetTest(UserApiTestCase):
|
||||
)
|
||||
|
||||
|
||||
class UserPreferenceViewSetTest(UserApiTestCase):
|
||||
class UserPreferenceViewSetTest(CacheIsolationTestCase, UserApiTestCase):
|
||||
"""
|
||||
Test cases covering the User Preference DRF view class and its various behaviors
|
||||
"""
|
||||
LIST_URI = USER_PREFERENCE_LIST_URI
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(UserPreferenceViewSetTest, self).setUp()
|
||||
self.detail_uri = self.get_uri_for_pref(self.prefs[0])
|
||||
@@ -1725,12 +1728,16 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
|
||||
|
||||
@httpretty.activate
|
||||
@ddt.ddt
|
||||
class ThirdPartyRegistrationTestMixin(ThirdPartyOAuthTestMixin):
|
||||
class ThirdPartyRegistrationTestMixin(ThirdPartyOAuthTestMixin, CacheIsolationTestCase):
|
||||
"""
|
||||
Tests for the User API registration endpoint with 3rd party authentication.
|
||||
"""
|
||||
CREATE_USER = False
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
__test__ = False
|
||||
|
||||
def setUp(self):
|
||||
super(ThirdPartyRegistrationTestMixin, self).setUp()
|
||||
self.url = reverse('user_api_registration')
|
||||
@@ -1847,6 +1854,8 @@ class TestFacebookRegistrationView(
|
||||
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinFacebook, TransactionTestCase
|
||||
):
|
||||
"""Tests the User API registration endpoint with Facebook authentication."""
|
||||
__test__ = True
|
||||
|
||||
def test_social_auth_exception(self):
|
||||
"""
|
||||
According to the do_auth method in social.backends.facebook.py,
|
||||
@@ -1863,6 +1872,8 @@ class TestGoogleRegistrationView(
|
||||
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinGoogle, TransactionTestCase
|
||||
):
|
||||
"""Tests the User API registration endpoint with Google authentication."""
|
||||
__test__ = True
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
0
openedx/core/djangolib/testing/__init__.py
Normal file
0
openedx/core/djangolib/testing/__init__.py
Normal file
122
openedx/core/djangolib/testing/utils.py
Normal file
122
openedx/core/djangolib/testing/utils.py
Normal file
@@ -0,0 +1,122 @@
|
||||
from django.core.cache import caches
|
||||
from django.test import TestCase, override_settings
|
||||
from django.conf import settings
|
||||
from django.contrib import sites
|
||||
|
||||
from request_cache.middleware import RequestCache
|
||||
|
||||
|
||||
class CacheIsolationMixin(object):
|
||||
"""
|
||||
This class can be used to enable specific django caches for
|
||||
specific the TestCase that it's mixed into.
|
||||
|
||||
Usage:
|
||||
|
||||
Use the ENABLED_CACHES to list the names of caches that should
|
||||
be enabled in the context of this TestCase. These caches will
|
||||
use a loc_mem_cache with the default settings.
|
||||
|
||||
Set the class variable CACHES to explicitly specify the cache settings
|
||||
that should be overridden. This class will insert those values into
|
||||
django.conf.settings, and will reset all named caches before each
|
||||
test.
|
||||
|
||||
If both CACHES and ENABLED_CACHES are not None, raises an error.
|
||||
"""
|
||||
|
||||
CACHES = None
|
||||
ENABLED_CACHES = None
|
||||
__settings_override = None
|
||||
|
||||
@classmethod
|
||||
def start_cache_isolation(cls):
|
||||
"""
|
||||
Start cache isolation by overriding the settings.CACHES and
|
||||
flushing the cache.
|
||||
"""
|
||||
cache_settings = None
|
||||
if cls.CACHES is not None and cls.ENABLED_CACHES is not None:
|
||||
raise Exception(
|
||||
"Use either CACHES or ENABLED_CACHES, but not both"
|
||||
)
|
||||
|
||||
if cls.CACHES is not None:
|
||||
cache_settings = cls.CACHES
|
||||
elif cls.ENABLED_CACHES is not None:
|
||||
cache_settings = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||
}
|
||||
}
|
||||
|
||||
cache_settings.update({
|
||||
cache_name: {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
'LOCATION': cache_name,
|
||||
'KEY_FUNCTION': 'util.memcache.safe_key',
|
||||
} for cache_name in cls.ENABLED_CACHES
|
||||
})
|
||||
|
||||
if cache_settings is None:
|
||||
return
|
||||
|
||||
cls.__settings_override = override_settings(CACHES=cache_settings)
|
||||
cls.__settings_override.__enter__()
|
||||
|
||||
# Start with empty caches
|
||||
cls.clear_caches()
|
||||
|
||||
@classmethod
|
||||
def end_cache_isolation(cls):
|
||||
"""
|
||||
End cache isolation by flushing the cache and then returning
|
||||
settings.CACHES to its original state.
|
||||
"""
|
||||
# Make sure that cache contents don't leak out after the isolation is ended
|
||||
cls.clear_caches()
|
||||
|
||||
if cls.__settings_override is not None:
|
||||
cls.__settings_override.__exit__(None, None, None)
|
||||
cls.__settings_override = None
|
||||
|
||||
@classmethod
|
||||
def clear_caches(cls):
|
||||
"""
|
||||
Clear all of the caches defined in settings.CACHES.
|
||||
"""
|
||||
# N.B. As of 2016-04-20, Django won't return any caches
|
||||
# from django.core.cache.caches.all() that haven't been
|
||||
# accessed using caches[name] previously, so we loop
|
||||
# over our list of overridden caches, instead.
|
||||
for cache in settings.CACHES:
|
||||
caches[cache].clear()
|
||||
|
||||
# The sites framework caches in a module-level dictionary.
|
||||
# Clear that.
|
||||
sites.models.SITE_CACHE.clear()
|
||||
|
||||
RequestCache.clear_request_cache()
|
||||
|
||||
|
||||
class CacheIsolationTestCase(CacheIsolationMixin, TestCase):
|
||||
"""
|
||||
A TestCase that isolates caches (as described in
|
||||
:py:class:`CacheIsolationMixin`) at class setup, and flushes the cache
|
||||
between every test.
|
||||
"""
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(CacheIsolationTestCase, cls).setUpClass()
|
||||
cls.start_cache_isolation()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.end_cache_isolation()
|
||||
super(CacheIsolationTestCase, cls).tearDownClass()
|
||||
|
||||
def setUp(self):
|
||||
super(CacheIsolationTestCase, self).setUp()
|
||||
|
||||
self.clear_caches()
|
||||
self.addCleanup(self.clear_caches)
|
||||
@@ -15,6 +15,7 @@ from openedx.core.djangoapps.credentials.models import CredentialsApiConfig
|
||||
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin, CredentialsDataMixin
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from openedx.core.lib.edx_api_utils import get_edx_api_data
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
@@ -24,8 +25,11 @@ LOGGER_NAME = 'openedx.core.lib.edx_api_utils'
|
||||
|
||||
@attr('shard_2')
|
||||
class TestApiDataRetrieval(CredentialsApiConfigMixin, CredentialsDataMixin, ProgramsApiConfigMixin, ProgramsDataMixin,
|
||||
TestCase):
|
||||
CacheIsolationTestCase):
|
||||
"""Test utility for API data retrieval."""
|
||||
|
||||
ENABLED_CACHES = ['default']
|
||||
|
||||
def setUp(self):
|
||||
super(TestApiDataRetrieval, self).setUp()
|
||||
ClientFactory(name=CredentialsApiConfig.OAUTH2_CLIENT_NAME, client_type=CONFIDENTIAL)
|
||||
|
||||
Reference in New Issue
Block a user