Testing A Reusable Django App
Introduction Link to heading
I recently created a reuseable django app and was wondering how I’d go about testing it without creating an entire django application. Lucky for us, django has got an example for how to go about it.
The example was not enough for me as my application consisted of a couple of REST apis and using the above example required me to add a few more things, so lets go through the steps that were required.
App Stucture Link to heading
For the purpose of this post, our reusable app will be called work.
Our app directory stucture will be the usual Django structure.
work/
__init__.py
models.py
urls.py
serializers.py
views.py
tests.py
To start testing this app we have to change a few things in our directory structure.
- Create a
testspackage outside theworkdirectory. - Move the
work/tests.pyfile to thetestsdirectory. - Create a file called
runtests.pyat the top level, outside of theworkapp. This file will be used to run the tests.
This is how the strcture will look after the above changes.
runtests.py
work/
__init__.py
models.py
urls.py
serializers.py
views.py
tests/
__init__.py
tests.py
Setup Test Runner Link to heading
Our goal is to run runtests.py tests and have all our tests in tests/tests.py ran. So what needs to be in the runtests.py file?
The example provided by django is enough to get us going
#!/usr/bin/env python
import os
import sys
import django
from django.conf import settings
from django.test.utils import get_runner
if __name__ == "__main__":
os.environ["DJANGO_SETTINGS_MODULE"] = "tests.test_settings"
django.setup()
TestRunner = get_runner(settings)
test_runner = TestRunner()
failures = test_runner.run_tests(["tests"])
sys.exit(bool(failures))
Off the bat we see that we will need django itself, so lets create a virtualenv for our work app and install some dependencies.
In our requirements_tests.txt file lets add django~=3.2 and django-restframework~=3.14.
We can then pip install -r requirements_tests.txt.
This requirements file will just be used to install the dependencies for testing
In the runtests.py file we see that we need to configure a settings module tests.test_settings.
So lets create a minimalist settings file and place it in tests/test_settings.py.
In general, you only add the settings that you need for your app, so for our work app we need the following.
So just a reminder our work app is a set of REST apis with some authentication and database queries so we’ll need to add the required settings such as django.contrib.auth, rest_framework app, database settings and any other dependencies.
This is what our test_settings.py will now look like.
SECRET_KEY = 'fake-key'
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'rest_framework',
'work',
]
ROOT_URLCONF = 'tests.urls'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
}
}
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
TIME_ZONE = 'UTC'
USE_TZ = True
If you are not sure what settings you need to add, what you can also do is to run runtests.py tests and see what type or errors you get.
One of the errors I came across was the need to set a ROOT_URLCONF since I’m mainly using urls in my app.
This involved creating a urls.py file in the tests/ directory and adding the following
from django.urls import include, path
from work.urls import router
urlpatterns = [
path('work/', include(router.urls)),
]
which is what you’d add to a reqular django project.
So now our tests/ directory looks as follows
tests/
__init__.py
tests.py
test_settings.py
urls.py
with this in place we can now run tests just for our reusable app. So the full directory structure:
requirements_test.txt
runtests.py
work/
__init__.py
models.py
urls.py
serializers.py
views.py
tests/
__init__.py
tests.py
test_settings.py
urls.py
Github Action Link to heading
If you use github, you can use github actions to run tests everytime you push to your master branch. Lets create a github action for our work app.
- Create a directory
.github/workflows/ - Create a file called
work.ymland place it in theworkflowsdirectory.
In the work.yml action file
name: Work
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8
if [ -f requirements_test.txt ]; then pip install -r requirements_test.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
- name: Run Tests
run: |
python runtests.py
The name of this action is work and it should start running when we push to the main branch or there is a pull request. When a pull is triggered a job called test should start in a container based on the ubuntu-latest image.
There are various steps that should take places such as the setup of environments, actions/setup-python@v3 and any other dependencies, in our case we need to install the packages from the requirements_test.txt file. Once that is done the last step is to actually run the tests in the step named Run Tests.
And That’s it.
Conclusion Link to heading
With the above setup, we should now be able to independently test a Django application without adding the app to an existing Django project.