Skip to content

Chapter 15: Testing ​

15.1 Unit Testing ​

TestCase Class ​

Django provides a powerful testing framework based on Python's unittest module:

python
# tests.py
from django.test import TestCase
from django.contrib.auth.models import User
from django.urls import reverse
from .models import Article, Category, Tag

class ArticleModelTest(TestCase):
    """Article model test"""
    
    def setUp(self):
        """Test setup"""
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        self.category = Category.objects.create(
            name='Test Category',
            slug='test-category'
        )
        self.tag = Tag.objects.create(
            name='Test Tag',
            slug='test-tag'
        )
    
    def test_article_creation(self):
        """Test article creation"""
        article = Article.objects.create(
            title='Test Article',
            slug='test-article',
            author=self.user,
            category=self.category,
            content='This is the content of the test article',
            excerpt='This is the excerpt of the test article',
            status='published'
        )
        article.tags.add(self.tag)
        
        # Verify the article is created correctly
        self.assertEqual(article.title, 'Test Article')
        self.assertEqual(article.author, self.user)
        self.assertEqual(article.category, self.category)
        self.assertIn(self.tag, article.tags.all())
        self.assertTrue(article.is_published)
    
    def test_article_string_representation(self):
        """Test article string representation"""
        article = Article.objects.create(
            title='Test Article Title',
            slug='test-article',
            author=self.user,
            content='Content'
        )
        self.assertEqual(str(article), 'Test Article Title')
    
    def test_article_get_absolute_url(self):
        """Test article absolute URL"""
        article = Article.objects.create(
            title='Test Article',
            slug='test-article',
            author=self.user,
            content='Content'
        )
        expected_url = reverse('blog:article_detail', kwargs={'slug': 'test-article'})
        self.assertEqual(article.get_absolute_url(), expected_url)

class CategoryModelTest(TestCase):
    """Category model test"""
    
    def test_category_creation(self):
        """Test category creation"""
        category = Category.objects.create(
            name='Technology',
            description='Technology related articles'
        )
        self.assertEqual(category.name, 'Technology')
        self.assertEqual(category.slug, 'ji-zhu')  # Auto-generated slug
        self.assertEqual(str(category), 'Technology')
    
    def test_category_slug_generation(self):
        """Test category slug auto-generation"""
        category = Category.objects.create(name='Python Programming')
        self.assertEqual(category.slug, 'pythonbian-cheng')

class ArticleViewTest(TestCase):
    """Article view test"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass123'
        )
        self.category = Category.objects.create(name='Test Category')
        self.article = Article.objects.create(
            title='Test Article',
            author=self.user,
            category=self.category,
            content='Test Content',
            status='published'
        )
    
    def test_article_list_view(self):
        """Test article list view"""
        response = self.client.get(reverse('blog:article_list'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Test Article')
        self.assertTemplateUsed(response, 'blog/article_list.html')
    
    def test_article_detail_view(self):
        """Test article detail view"""
        response = self.client.get(
            reverse('blog:article_detail', kwargs={'slug': self.article.slug})
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Test Article')
        self.assertContains(response, 'Test Content')
        self.assertTemplateUsed(response, 'blog/article_detail.html')
    
    def test_article_detail_view_404(self):
        """Test non-existent article detail view"""
        response = self.client.get(
            reverse('blog:article_detail', kwargs={'slug': 'non-existent'})
        )
        self.assertEqual(response.status_code, 404)

# Use factory pattern to simplify test data creation
# factories.py
import factory
from django.contrib.auth.models import User
from .models import Article, Category, Tag

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User
    
    username = factory.Sequence(lambda n: f"user{n}")
    email = factory.LazyAttribute(lambda obj: f"{obj.username}@example.com")
    password = factory.PostGenerationMethodCall('set_password', 'defaultpass123')

class CategoryFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Category
    
    name = factory.Sequence(lambda n: f"Category{n}")
    description = factory.Faker('text', max_nb_chars=200)

class TagFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Tag
    
    name = factory.Sequence(lambda n: f"Tag{n}")

class ArticleFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Article
    
    title = factory.Faker('sentence', nb_words=6)
    content = factory.Faker('text', max_nb_chars=1000)
    excerpt = factory.Faker('text', max_nb_chars=200)
    author = factory.SubFactory(UserFactory)
    category = factory.SubFactory(CategoryFactory)
    status = 'published'

# Test example using factories
class ArticleFactoryTest(TestCase):
    """Test example using factories"""
    
    def test_create_multiple_articles(self):
        """Test creating multiple articles"""
        # Create 5 articles
        articles = ArticleFactory.create_batch(5)
        self.assertEqual(len(articles), 5)
        self.assertEqual(Article.objects.count(), 5)
    
    def test_create_article_with_tags(self):
        """Test creating article with tags"""
        tags = TagFactory.create_batch(3)
        article = ArticleFactory(tags=tags)
        self.assertEqual(article.tags.count(), 3)

Database Testing ​

Database related tests:

python
# test_models.py
from django.test import TestCase
from django.db import IntegrityError
from django.core.exceptions import ValidationError
from .models import Article, Category, Tag

class ModelDatabaseTest(TestCase):
    """Model database test"""
    
    def setUp(self):
        self.user = UserFactory()
        self.category = CategoryFactory()
    
    def test_unique_slug_constraint(self):
        """Test unique slug constraint"""
        ArticleFactory(slug='unique-slug')
        with self.assertRaises(IntegrityError):
            ArticleFactory(slug='unique-slug')
    
    def test_category_name_unique(self):
        """Test category name uniqueness"""
        CategoryFactory(name='Unique Category')
        with self.assertRaises(IntegrityError):
            CategoryFactory(name='Unique Category')
    
    def test_article_validation(self):
        """Test article validation"""
        article = Article(
            title='',  # Empty title should raise validation error
            author=self.user,
            content='Content'
        )
        with self.assertRaises(ValidationError):
            article.full_clean()
    
    def test_article_save_method(self):
        """Test article save method"""
        article = ArticleFactory(
            title='Test Article',
            excerpt=''  # Empty excerpt should be auto-generated
        )
        self.assertTrue(len(article.excerpt) > 0)
    
    def test_foreign_key_cascade(self):
        """Test foreign key cascade delete"""
        article = ArticleFactory(category=self.category)
        category_id = self.category.id
        
        # Delete category
        self.category.delete()
        
        # Article should still exist but category is null
        article.refresh_from_db()
        self.assertIsNone(article.category)
        self.assertTrue(Article.objects.filter(id=article.id).exists())

Form Testing ​

Form testing ensures form validation and processing logic is correct:

python
# test_forms.py
from django.test import TestCase
from .forms import ArticleForm, CommentForm
from .models import Article, Category, Tag

class FormTest(TestCase):
    """Form test"""
    
    def setUp(self):
        self.user = UserFactory()
        self.category = CategoryFactory()
        self.tag = TagFactory()
    
    def test_article_form_valid_data(self):
        """Test article form with valid data"""
        form_data = {
            'title': 'Test Article',
            'category': self.category.id,
            'content': 'Article Content',
            'excerpt': 'Article Excerpt',
            'status': 'published'
        }
        form = ArticleForm(data=form_data)
        self.assertTrue(form.is_valid())
    
    def test_article_form_invalid_data(self):
        """Test article form with invalid data"""
        form_data = {
            'title': '',  # Empty title
            'content': '',  # Empty content
        }
        form = ArticleForm(data=form_data)
        self.assertFalse(form.is_valid())
        self.assertIn('title', form.errors)
        self.assertIn('content', form.errors)
    
    def test_article_form_save(self):
        """Test article form save"""
        form_data = {
            'title': 'Test Article',
            'category': self.category.id,
            'content': 'Article Content',
            'excerpt': 'Article Excerpt',
            'status': 'published'
        }
        form = ArticleForm(data=form_data)
        self.assertTrue(form.is_valid())
        
        article = form.save(commit=False)
        article.author = self.user
        article.save()
        
        self.assertEqual(article.title, 'Test Article')
        self.assertEqual(article.author, self.user)
    
    def test_comment_form_valid_data(self):
        """Test comment form with valid data"""
        form_data = {
            'content': 'This is a comment'
        }
        form = CommentForm(data=form_data)
        self.assertTrue(form.is_valid())
    
    def test_comment_form_empty_content(self):
        """Test comment form with empty content"""
        form_data = {
            'content': ''
        }
        form = CommentForm(data=form_data)
        self.assertFalse(form.is_valid())
        self.assertIn('content', form.errors)

View Testing ​

View testing ensures HTTP request handling is correct:

python
# test_views.py
from django.test import TestCase
from django.urls import reverse
from django.contrib.auth.models import User
from .models import Article, Category

class ViewTest(TestCase):
    """View test"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass123'
        )
        self.staff_user = User.objects.create_user(
            username='staffuser',
            password='staffpass123',
            is_staff=True
        )
        self.category = Category.objects.create(name='Test Category')
        self.article = Article.objects.create(
            title='Test Article',
            author=self.user,
            category=self.category,
            content='Test Content',
            status='published'
        )
    
    def test_home_page_status_code(self):
        """Test home page status code"""
        response = self.client.get(reverse('blog:home'))
        self.assertEqual(response.status_code, 200)
    
    def test_home_page_contains_correct_html(self):
        """Test home page contains correct HTML"""
        response = self.client.get(reverse('blog:home'))
        self.assertContains(response, 'Test Article')
        self.assertContains(response, '<title>')
    
    def test_home_page_uses_correct_template(self):
        """Test home page uses correct template"""
        response = self.client.get(reverse('blog:home'))
        self.assertTemplateUsed(response, 'blog/article_list.html')
    
    def test_article_detail_view_with_existing_article(self):
        """Test existing article detail view"""
        response = self.client.get(
            reverse('blog:article_detail', kwargs={'slug': self.article.slug})
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Test Article')
    
    def test_article_detail_view_with_nonexistent_article(self):
        """Test non-existent article detail view"""
        response = self.client.get(
            reverse('blog:article_detail', kwargs={'slug': 'nonexistent'})
        )
        self.assertEqual(response.status_code, 404)
    
    def test_create_article_view_requires_login(self):
        """Test create article view requires login"""
        response = self.client.get(reverse('blog:article_create'))
        # Should redirect to login page
        self.assertEqual(response.status_code, 302)
    
    def test_create_article_view_with_authenticated_user(self):
        """Test create article view with authenticated user"""
        self.client.login(username='testuser', password='testpass123')
        response = self.client.get(reverse('blog:article_create'))
        self.assertEqual(response.status_code, 200)
    
    def test_search_view(self):
        """Test search view"""
        response = self.client.get(reverse('blog:search'), {'q': 'Test'})
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Test Article')
    
    def test_category_detail_view(self):
        """Test category detail view"""
        response = self.client.get(
            reverse('blog:category_detail', kwargs={'slug': self.category.slug})
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Test Article')

With these unit tests, you can ensure that each component of your application works as expected and quickly identify issues when code changes.

Summary ​

Key points for Django unit testing:

  1. ✅ Use TestCase class for model, view, and form testing
  2. ✅ Implement database testing to verify data integrity and constraints
  3. ✅ Write form tests to ensure validation logic is correct
  4. ✅ Execute view tests to verify HTTP request handling
  5. ✅ Use factory pattern to simplify test data creation

A good testing system is key to ensuring code quality and application stability.

Next ​

We will learn about integration testing.

15.2 Integration Testing →

Contents ​

Back to Course Outline

Released under the [BY-NC-ND License](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.en).