Let's say I have an Article
model, like this:
from django.db import models
class Article(models.Model):
author = models.CharField(max_length=100)
title = models.CharField(max_length=200)
body = models.TextField()
This is naively simple compared to my actual usage (author
should be a ForeignKey
to another model, etc.), but this way it's more clear.
Imagine I want to list titles of all the articles by certain authors, yet keeping each author's pieces together. It could be represented as a list of lists:
def get_beatles_articles_titles():
beatles = [
"John Lennon",
"Paul McCartney",
"George Harrison",
"Ringo Starrr",
]
return [article.author for author in beatles
for article in Article.objects.filter(author=author)]
Oh, a nested list comprehension, so our method isn't that simple. There's a big chance of a bug being somewhere here, so we should test it somehow! The easiest solution seems to be creating some Article
instances corresponding to each author (and saving them in the database) and check if all of them are properly fetched.
from django.test import TestCase
from models import Article
from views import get_beatles_articles_titles
class ArticlesTitlesTestCase(TestCase):
def test_that_every_beatles__article_is_fetched(self):
Article.objects.create(author="John Lennon", title="John's")
Article.objects.create(author="Paul McCartney", title="Paul's")
Article.objects.create(author="George Harrison", title="George's")
Article.objects.create(author="Ringo Starr", title="Ringo's")
self.assertEqual(get_beatles_articles_titles(), [
["John's"],
["Paul's"],
["George's"],
["Ringo's"]
])
Running that test we can see that there's a typo in our original code, so it proved its usefulness.
However, accessing database is frowned upon in favor of mocking things (no wonder, I've experienced that time difference can be significant). What can be mocked in the test above? I'm particularly anxious about correctness of my .filter
query (it might get pretty complex), so I don't want to guess QuerySet
s a DB would give me.
Ideally, I'd like to use something like this (pseudo code follows):
johns_article = Article(author="John Lennon")
fake_query = MockQuery(author__contains="John")
assertTrue(fakeQuery.contains(johns_article))
accessing database is frowned upon in favor of mocking things
Why is this?? Common concerns are:
If you need to test your database interaction the only way would be to run your test against a database. Django has you covered and has created a framework that addresses all the concerns listed above. The django TestCase
handles all of this for you. It manages a test database, provides tools for provisioning, executes all tests in a transactions and quickly cleans up by rolling back after every transaction.
I definitely agree that for quick tests they should be done on the unit level and stub all collaboration and file/socket access, but in this case django should have you covered.
You're already using the django database, which is being provisioned for every test run, so why not access it?
So basically your test is right on, django created the tools for it, it is not an anti pattern at all. A common strategy is to have multiple tiers of tests. You could have a "unit test" or "small test" framework and test runner that does no IO where most of your tests live, where all collaboration is mocked/stubbed. Then implement a number of tests in django TestCase
to test database interaction.
If youre starting with testing/django there are a couple of things that could help in the long run:
get_beatles_articles_titles
can be optimized and done with less queriesbulk_create
method instead of multiple create
methods