chore(django-spanner): add unit test coverage#17815
Conversation
…unit test coverage threshold
There was a problem hiding this comment.
Code Review
This pull request expands test coverage for the django-google-spanner package, adding unit tests for database creation, features, initialization, compiler edge cases, lookups, operations, and schema editor functionality, while also integrating mockserver tests into the main test run. The review feedback correctly identifies several instances where direct modification of global settings, connection features, or module states could lead to test pollution and flaky test runs, and suggests using mock patching or try-finally blocks to ensure clean test isolation.
| with mock.patch.dict(os.environ, {"RUNNING_SPANNER_BACKEND_TESTS": "1", "SPANNER_EMULATOR_HOST": "localhost:9010"}): | ||
| with mock.patch.object(django_spanner, "USE_EMULATOR", True): | ||
| importlib.reload(django_spanner.features) | ||
| feat = django_spanner.features.DatabaseFeatures(self.db_wrapper) | ||
| self.assertFalse(feat.supports_foreign_keys) | ||
| self.assertFalse(feat.supports_json_field) | ||
| self.assertTrue(any("test_loaddata" in test for test in feat.skip_tests)) | ||
|
|
||
| importlib.reload(django_spanner.features) |
There was a problem hiding this comment.
If any assertion fails during the test, the module django_spanner.features will not be reloaded to its original state, which can cause test pollution and flaky failures in subsequent tests. Wrap the test logic in a try...finally block to guarantee that the module is always reloaded.
| with mock.patch.dict(os.environ, {"RUNNING_SPANNER_BACKEND_TESTS": "1", "SPANNER_EMULATOR_HOST": "localhost:9010"}): | |
| with mock.patch.object(django_spanner, "USE_EMULATOR", True): | |
| importlib.reload(django_spanner.features) | |
| feat = django_spanner.features.DatabaseFeatures(self.db_wrapper) | |
| self.assertFalse(feat.supports_foreign_keys) | |
| self.assertFalse(feat.supports_json_field) | |
| self.assertTrue(any("test_loaddata" in test for test in feat.skip_tests)) | |
| importlib.reload(django_spanner.features) | |
| try: | |
| with mock.patch.dict(os.environ, {"RUNNING_SPANNER_BACKEND_TESTS": "1", "SPANNER_EMULATOR_HOST": "localhost:9010"}): | |
| with mock.patch.object(django_spanner, "USE_EMULATOR", True): | |
| importlib.reload(django_spanner.features) | |
| feat = django_spanner.features.DatabaseFeatures(self.db_wrapper) | |
| self.assertFalse(feat.supports_foreign_keys) | |
| self.assertFalse(feat.supports_json_field) | |
| self.assertTrue(any("test_loaddata" in test for test in feat.skip_tests)) | |
| finally: | |
| importlib.reload(django_spanner.features) |
| compiler = SQLCompiler(qs4.query, self.connection, "default") | ||
| compiler.connection.features.supports_slicing_ordering_in_compound = True | ||
| compiler.query.high_mark = 10 | ||
| sql, params = compiler.get_combinator_sql("union", False) | ||
| self.assertTrue(len(sql) > 0) |
There was a problem hiding this comment.
Modifying compiler.connection.features.supports_slicing_ordering_in_compound directly can pollute other tests if the connection is shared. Use mock.patch.object to safely patch the feature attribute and ensure it is restored after the test.
| compiler = SQLCompiler(qs4.query, self.connection, "default") | |
| compiler.connection.features.supports_slicing_ordering_in_compound = True | |
| compiler.query.high_mark = 10 | |
| sql, params = compiler.get_combinator_sql("union", False) | |
| self.assertTrue(len(sql) > 0) | |
| compiler = SQLCompiler(qs4.query, self.connection, "default") | |
| with mock.patch.object(compiler.connection.features, "supports_slicing_ordering_in_compound", True): | |
| compiler.query.high_mark = 10 | |
| sql, params = compiler.get_combinator_sql("union", False) | |
| self.assertTrue(len(sql) > 0) |
| def test_mark_skips(self): | ||
| with mock.patch("django.conf.settings.INSTALLED_APPS", ["django.contrib.contenttypes"]): | ||
| self.db_wrapper.features.skip_tests = ( | ||
| "django.contrib.contenttypes.models.ContentType", | ||
| ) | ||
| self.creation.mark_skips() |
There was a problem hiding this comment.
Modifying self.db_wrapper.features.skip_tests directly can pollute other tests that share the same connection wrapper. Use mock.patch.object to safely patch skip_tests so that it is automatically restored after the test block.
def test_mark_skips(self):
with mock.patch("django.conf.settings.INSTALLED_APPS", ["django.contrib.contenttypes"]):
with mock.patch.object(self.db_wrapper.features, "skip_tests", ("django.contrib.contenttypes.models.ContentType",)):
self.creation.mark_skips()| for use_tz in [True, False]: | ||
| settings.USE_TZ = use_tz | ||
| tz = "IST" if use_tz else "UTC" | ||
| self.assertIn(tz, ops.datetime_extract_sql("dayofweek", "f", None, "IST")[0]) | ||
| self.assertIn(tz, ops.datetime_cast_time_sql("f", None, "IST")[0]) | ||
| settings.USE_TZ = True |
There was a problem hiding this comment.
Modifying settings.USE_TZ directly inside the loop can leave it in an incorrect state if any assertion fails. Wrap the loop in a try...finally block to guarantee that settings.USE_TZ is always restored to True.
| for use_tz in [True, False]: | |
| settings.USE_TZ = use_tz | |
| tz = "IST" if use_tz else "UTC" | |
| self.assertIn(tz, ops.datetime_extract_sql("dayofweek", "f", None, "IST")[0]) | |
| self.assertIn(tz, ops.datetime_cast_time_sql("f", None, "IST")[0]) | |
| settings.USE_TZ = True | |
| try: | |
| for use_tz in [True, False]: | |
| settings.USE_TZ = use_tz | |
| tz = "IST" if use_tz else "UTC" | |
| self.assertIn(tz, ops.datetime_extract_sql("dayofweek", "f", None, "IST")[0]) | |
| self.assertIn(tz, ops.datetime_cast_time_sql("f", None, "IST")[0]) | |
| finally: | |
| settings.USE_TZ = True |
df87059 to
2f6e355
Compare
Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:
Fixes #<issue_number_goes_here> 🦕