Posted Feb. 25, 2022
I have an old Django project where I'm working on achieving 100% unit test coverage to prepare for a major overhaul, so I find myself writing unit tests for the sorts of things that Django developers might not normally get around to. Recently I was struggling to test a situation in my Django settings file where I have GeoDjango-related settings that are different for Windows than they are for Linux. I run the application under Docker myself, so it's always Linux, but I wanted to leave these settings intact for now.
# Where to find SpatiaLite and GDAL libraries, necessary to support geodatabase stuff in SQLite if platform.system() == 'Windows': # For Windows, use the following setting. Get mod_spatialite from http://www.gaia-gis.it/gaia-sins/ # Put all DLLs in the same directory with the Python executable (system and probably also virtualenv) SPATIALITE_LIBRARY_PATH = 'mod_spatialite' # For Windows, set the location of the GDAL DLL and add the GDAL directory to your PATH for the other DLLs # Under Linux it doesn't seem to be necessary GDAL_LIBRARY_PATH = 'C:\Program Files\GDAL\gdal201.dll' # A Fallback DB_DIR = BASE_DIR else: # For Linux SPATIALITE_LIBRARY_PATH = '/usr/local/lib/mod_spatialite.so'
I knew how to mock platform.system(), but I was tearing my hair out trying to figure out how to test both conditions. The settings had already been loaded by the time the testrunner got to my tests, so whatever was set at the time was what I got. After a lot of googling, and combing Stack Overflow and various blog entries, I came to the conclusion that this isn't something that people really test, or at least not often.
An unanswered question on Stack Overflow pointed me to importlib.reload
, so I gave it a shot. The settings file was imported in my test file as from django.conf import settings
, which is how the Django docs say that it should be done. This is how I usually import it too, but I got the following error with importlib.reload(settings)
:
====================================================================== ERROR: test_settings_for_linux (letterpress.tests.test_settings.TestSettingsForLinuxTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/usr/local/lib/python3.5/unittest/mock.py", line 1159, in patched return func(*args, **keywargs) File "/code/letterpress/tests/test_settings.py", line 29, in test_settings_for_linux importlib.reload(settings) File "/usr/local/lib/python3.5/importlib/__init__.py", line 139, in reload raise TypeError("reload() argument must be a module") TypeError: reload() argument must be a module
Clearly that wasn't the way to go. After a lot of trial and error and taking another look at that Stack Overflow question, I found the solution. If I imported my application's Django settings directly (i.e. import letterpress.settings
), I finally got reloading the settings to work by using importlib.reload(letterpress.settings)
.
Here's the finished product, using patch
to switch between Linux and Windows:
import importlib from unittest.mock import patch from django.conf import settings from django.test import SimpleTestCase # Normally Django settings should be imported as "from django.conf import settings" # but here we need to import our settings module explicitly so we can manually reload it import letterpress.settings class TestGeoDjangoSettingsTestCase(SimpleTestCase): """ SPATIALITE_LIBRARY_PATH and GDAL_LIBRARY_PATH settings should be different for Linux and Windows """ @patch('platform.system', autospec=True) def test_settings_for_linux(self, mock_system): """ GDAL_LIBRARY_PATH should be set for Windows only If platform.system() returns something else, settings.GDAL_LIBRARY_PATH shouldn't be set """ # Mock platform.system() to have it return 'Linux' mock_system.return_value = 'Linux' # Reload Django settings importlib.reload(letterpress.settings) self.assertTrue(letterpress.settings.SPATIALITE_LIBRARY_PATH.endswith('.so'), "On Linux, SPATIALITE_LIBRARY_PATH should end with '.so'") with self.assertRaises(AttributeError): print(letterpress.settings.GDAL_LIBRARY_PATH) @patch('platform.system', autospec=True) def test_settings_for_windows(self, mock_system): """ GDAL_LIBRARY_PATH should be set for Windows only If platform.system() returns 'Windows', settings.GDAL_LIBRARY_PATH should be set """ # Mock platform.system() to have it return 'Windows' mock_system.return_value = 'Windows' # Reload Django settings importlib.reload(letterpress.settings) self.assertFalse(letterpress.settings.SPATIALITE_LIBRARY_PATH.endswith('.so'), "On Windows, SPATIALITE_LIBRARY_PATH should not end with '.so'") self.assertFalse(letterpress.settings.GDAL_LIBRARY_PATH == None, 'On Windows, GDAL_LIBRARY_PATH should be set')