Testing a nanodjango script¶
As you write nanodjango scripts, you’ll often want to write tests that:
check the script actually loads (
manage.py check, ormakemigrations/migrate), andrun it for real and check it returns the values you expect from its URLs.
Nanodjango uses exactly this pattern to test its own example scripts, and exposes the helpers it uses as nanodjango.testing so you can
use them in your own project’s test suite.
Important
These helpers are for use with pytest
Running a command¶
The quickest way to test your script is to run a nanodjango command against it.
You can do this using the cmd helper; for example, we can run the nanodjango
equivalent of manage.py check and confirm it exits cleanly:
from nanodjango.testing import cmd
def test_myscript_check():
result = cmd("manage", "myscript.py", "check")
assert result.stderr.strip() == ""
assert result.stdout.strip() == (
"System check identified no issues (0 silenced)."
)
cmd() runs python -m nanodjango <script> <args> as a subprocess, and calls
pytest.fail() with the captured stdout/stderr if it returns a non-zero exit code.
You can run any nanodjango command with this; the most common will be to make sure your script’s models are migrated (just remember to use a test database):
cmd("manage", "myscript.py", "makemigrations")
cmd("manage", "myscript.py", "migrate")
See API reference for more details.
Running a script and checking its URLs¶
While you could technically call cmd("run"), it would run your nanodjango site
in a blocking subprocess and lock up your test. Instead, we have a pair of helpers:
nanodjango_process- context manager to launch a nanodjango processrunserver- context manager to handle the initialisation of ananodjango_processthat calls runserver
You can use them like this:
import urllib.request
from nanodjango.testing import cmd, nanodjango_process, runserver
TEST_SCRIPT = "myscript.py"
TEST_BIND = "127.0.0.1:8042"
def test_myscript_urls():
cmd("manage", TEST_SCRIPT, "makemigrations")
cmd("manage", TEST_SCRIPT, "migrate")
with (
nanodjango_process("manage", TEST_SCRIPT, "runserver", TEST_BIND) as handle,
runserver(handle),
):
response = urllib.request.urlopen(f"http://{TEST_BIND}/", timeout=10)
assert response.getcode() == 200
assert "expected content" in response.read().decode("utf-8")
nanodjango_process() starts python -m nanodjango <script> <args> and yields
its subprocess.Popen handle, terminating it when the block exits.
runserver() reads the process’s startup output to confirm the server actually
came up (rather than crashing) before you start making requests against it - pass
expect=EXPECT_UVICORN or expect=EXPECT_GUNICORN if your script runs under
those instead of Django’s dev server.
Because starting a server is slow, it’s usually best to do it once per test module and reuse it across tests with a fixture:
import urllib.request
import pytest
from nanodjango.testing import cmd, nanodjango_process, runserver
TEST_SCRIPT = "myscript.py"
TEST_BIND = "127.0.0.1:8042"
@pytest.fixture(scope="module")
def myscript():
cmd("manage", TEST_SCRIPT, "makemigrations")
cmd("manage", TEST_SCRIPT, "migrate")
with (
nanodjango_process("manage", TEST_SCRIPT, "runserver", TEST_BIND) as handle,
runserver(handle),
):
yield handle
def test_index(myscript):
response = urllib.request.urlopen(f"http://{TEST_BIND}/", timeout=10)
assert response.getcode() == 200
assert "expected content" in response.read().decode("utf-8")
def test_other_url(myscript):
response = urllib.request.urlopen(f"http://{TEST_BIND}/other/", timeout=10)
assert response.getcode() == 200
Testing app code without a script on disk¶
If you want to test a specific bit of nanodjango behaviour rather than a whole
example script - for example while writing a plugin, or reproducing an issue -
run_app_code() writes a string of Python to a temporary file and runs it,
without you needing to manage a fixture file yourself:
from nanodjango.testing import run_app_code
def test_debug_setting():
result = run_app_code("""
from nanodjango import Django
app = Django()
@app.route("/")
def index(request):
return "Hello"
from django.conf import settings
print("DEBUG:", settings.DEBUG)
""")
assert result.returncode == 0
assert "DEBUG: True" in result.stdout
Working directory¶
By default, cmd() and nanodjango_process() run relative to the directory of
the test file that called them, so paths like "myscript.py" or
"../examples/myscript.py" work the same way they would from the command line.
Pass an explicit cwd to cmd() to override this.