Skip to content

chore(django-spanner): add unit test coverage#17815

Draft
ohmayr wants to merge 5 commits into
mainfrom
fix-django-spanner-coverage
Draft

chore(django-spanner): add unit test coverage#17815
ohmayr wants to merge 5 commits into
mainfrom
fix-django-spanner-coverage

Conversation

@ohmayr

@ohmayr ohmayr commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

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:

  • Make sure to open an issue as a bug/issue before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)

Fixes #<issue_number_goes_here> 🦕

@ohmayr ohmayr changed the title Fix django spanner coverage chore(django-spanner): add unit test coverage Jul 21, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +19 to +27
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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)

Comment on lines +184 to +188
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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)

Comment on lines +20 to +25
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()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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()

Comment on lines +274 to 279
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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

@ohmayr
ohmayr force-pushed the fix-django-spanner-coverage branch from df87059 to 2f6e355 Compare July 21, 2026 21:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant