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).