Testing Guide
This guide covers how to author and run tests for the django-ldaporm project, including both the core library tests and demo application tests.
Prerequisites
Before running tests, ensure you have:
Virtual Environment: Activate the project’s virtual environment
source .venv/bin/activate
Dependencies: Install all required dependencies
uv sync --group demo dev
Test Structure
The project has two main test suites:
Core Library Tests (ldaporm/tests/): Tests for the ldaporm library itself
Demo Tests (sandbox/demo/tests/): Tests for the demo application
Core Library Tests
The core library tests are located in ldaporm/tests/ and cover:
Field implementations and validation
Model functionality
Manager operations
REST framework integration
Form handling
LDAP connection management
Running Core Tests
Run all core library tests:
python -m pytest ldaporm/tests/
Run specific test modules:
# Test fields
python -m pytest ldaporm/tests/test_f_class.py
# Test managers
python -m pytest ldaporm/tests/test_ldap_manager.py
# Test REST framework integration
python -m pytest ldaporm/tests/test_restframework.py
# Test forms
python -m pytest ldaporm/tests/test_forms.py
# Test models
python -m pytest ldaporm/tests/test_models.py
Run specific test classes:
# Test binary field functionality
python -m pytest ldaporm/tests/test_restframework.py::LdapModelSerializerTestCase::test_binary_field_serialization
# Test field validation
python -m pytest ldaporm/tests/test_f_class.py::FieldTestCase
# Test manager operations
python -m pytest ldaporm/tests/test_ldap_manager.py::ManagerTestCase
Run tests with verbose output:
python -m pytest ldaporm/tests/ -v
Run tests with coverage:
python -m pytest ldaporm/tests/ --cov=ldaporm --cov-report=html
Demo Application Tests
The demo application tests are located in sandbox/demo/tests/ and cover:
API endpoints
Model integration
Authentication
Business logic
End-to-end workflows
Running Demo Tests
Run all demo tests:
cd sandbox
python manage.py test --settings=demo.settings_test
Run specific demo test modules:
# Test API endpoints
python manage.py test --settings=demo.settings_test demo.api.tests
# Test core functionality
python manage.py test --settings=demo.settings_test demo.core.tests
# Test user management
python manage.py test --settings=demo.settings_test demo.users.tests
Run specific test classes:
python manage.py test --settings=demo.settings_test demo.api.tests.UserAPITestCase
python manage.py test --settings=demo.settings_test demo.core.tests.UserModelTestCase
Run tests with verbose output:
python manage.py test -v 2
Integration Tests
Integration tests require a running LDAP server. The project provides Docker-based LDAP servers for testing.
Setting Up Test LDAP Server
Start the test LDAP server:
cd sandbox
docker-compose up -d ldap
Wait for the LDAP server to be ready:
docker-compose logs ldap
Running Integration Tests
Run integration tests with the LDAP server:
# Core library integration tests
python -m pytest ldaporm/tests/ -m "integration"
# Demo integration tests
cd sandbox
python manage.py test --settings=demo.settings_docker
Test Configuration
Test settings are configured in several ways:
Core Library Tests: Use Django test settings configured in test files
Demo Tests: Use demo/settings_docker.py for Docker-based testing
Integration Tests: Use LDAP server configuration from Docker Compose
Writing Tests
Guidelines for writing tests in the django-ldaporm project:
Test Structure
Follow this structure for new test files:
"""
Tests for [module name].
This module tests [brief description of what is being tested].
"""
import unittest
from unittest.mock import Mock, patch
from django.test import TestCase
from ldaporm import fields, models
class TestCaseName(TestCase):
"""Test cases for [specific functionality]."""
def setUp(self):
"""Set up test data."""
pass
def test_specific_functionality(self):
"""Test [specific functionality description]."""
# Arrange
# Act
# Assert
pass
Field Tests
When testing fields, follow this pattern:
def test_field_validation(self):
"""Test field validation."""
field = fields.CharField(max_length=50)
# Test valid value
value = field.to_python("test")
self.assertEqual(value, "test")
# Test invalid value
with self.assertRaises(ValidationError):
field.to_python(None)
Model Tests
When testing models, use mock LDAP connections:
@patch('ldaporm.models.get_ldap_connection')
def test_model_save(self, mock_connection):
"""Test model save operation."""
mock_conn = Mock()
mock_connection.return_value = mock_conn
user = TestUser(username='testuser', cn='Test User')
user.save()
mock_conn.add_s.assert_called_once()
REST Framework Tests
When testing REST framework integration:
def test_serializer_binary_field(self):
"""Test binary field serialization."""
class UserSerializer(LdapModelSerializer):
class Meta:
model = TestUser
# Test serialization
instance = Mock()
instance._meta.fields = [Mock(name='photo', __class__=fields.BinaryField)]
instance.photo = b'test_data'
serializer = UserSerializer()
result = serializer.to_representation(instance)
import base64
expected = base64.b64encode(b'test_data').decode('utf-8')
self.assertEqual(result['photo'], expected)
Demo Application Tests
When testing the demo application:
from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APITestCase
class UserAPITestCase(APITestCase):
"""Test user API endpoints."""
def setUp(self):
"""Set up test data."""
self.user_data = {
'username': 'testuser',
'cn': 'Test User',
'mail': 'test@example.com'
}
def test_create_user(self):
"""Test user creation via API."""
url = reverse('api:user-list')
response = self.client.post(url, self.user_data)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data['username'], 'testuser')
Test Data Management
Managing test data for LDAP-based tests:
Fixtures
Create test fixtures for consistent test data:
# test_fixtures.py
TEST_USER_DATA = {
'username': 'testuser',
'cn': 'Test User',
'mail': 'test@example.com',
'sn': 'User',
'givenName': 'Test'
}
TEST_GROUP_DATA = {
'cn': 'testgroup',
'description': 'Test Group'
}
Mock LDAP Data
Use mocks for LDAP data in unit tests:
@patch('ldaporm.managers.get_ldap_connection')
def test_manager_filter(self, mock_connection):
"""Test manager filtering."""
mock_conn = Mock()
mock_connection.return_value = mock_conn
# Mock LDAP search results
mock_conn.search_s.return_value = [
('cn=testuser,ou=users,dc=example,dc=com', {
'cn': [b'testuser'],
'mail': [b'test@example.com']
})
]
users = TestUser.objects.filter(cn='testuser')
self.assertEqual(len(users), 1)
Test Database Setup
For integration tests, set up test LDAP data:
def setUp(self):
"""Set up test LDAP data."""
# Add test user to LDAP
user_dn = 'cn=testuser,ou=users,dc=example,dc=com'
user_attrs = {
'objectClass': [b'person', b'organizationalPerson', b'inetOrgPerson'],
'cn': [b'testuser'],
'sn': [b'User'],
'mail': [b'test@example.com']
}
self.ldap_conn.add_s(user_dn, ldap.modlist.addModlist(user_attrs))
Continuous Integration
The project uses GitHub Actions for continuous integration. Tests are automatically run on:
Pull requests
Pushes to main branch
Scheduled runs
CI Configuration
The CI pipeline:
Sets up Python environment
Installs dependencies
Starts LDAP server containers
Runs core library tests
Runs demo application tests
Generates coverage reports
Local CI Simulation
Simulate CI locally:
# Run all tests as CI would
make test
# Run with Docker services
python -m pytest ldaporm/tests/ --cov=ldaporm
cd sandbox && python manage.py test --settings=demo.settings_test
Troubleshooting
Common test issues and solutions:
LDAP Connection Issues
If tests fail with LDAP connection errors:
# Check if LDAP server is running
docker-compose ps
# Restart LDAP server
docker-compose restart ldap
# Check LDAP server logs
docker-compose logs ldap
Test Isolation Issues
If tests interfere with each other:
def tearDown(self):
"""Clean up after each test."""
# Remove test data from LDAP
try:
self.ldap_conn.delete_s('cn=testuser,ou=users,dc=example,dc=com')
except ldap.NO_SUCH_OBJECT:
pass
Performance Issues
For slow tests:
# Run tests in parallel
python -m pytest ldaporm/tests/ -n auto
# Run only fast tests
python -m pytest ldaporm/tests/ -m "not slow"
Best Practices
Test Isolation: Each test should be independent and not rely on other tests
Mock External Dependencies: Use mocks for LDAP connections in unit tests
Use Descriptive Names: Test method names should clearly describe what is being tested
Arrange-Act-Assert: Structure tests with clear setup, execution, and verification phases
Test Edge Cases: Include tests for error conditions and boundary values
Use Fixtures: Create reusable test data and configurations
Documentation: Include docstrings explaining what each test verifies
Example Test Suite
Here’s a complete example of a test suite:
"""
Tests for User model functionality.
This module tests user creation, validation, and LDAP operations.
"""
import unittest
from unittest.mock import Mock, patch
from django.test import TestCase
from django.core.exceptions import ValidationError
from ldaporm import fields, models
class TestUser(models.Model):
"""Test user model."""
username = fields.CharField(max_length=50, primary_key=True)
cn = fields.CharField(max_length=100)
mail = fields.EmailField()
photo = fields.BinaryField(blank=True, null=True)
class Meta:
basedn = "ou=users,dc=example,dc=com"
objectclass = "person"
ldap_server = "test_server"
class UserModelTestCase(TestCase):
"""Test cases for User model."""
def setUp(self):
"""Set up test data."""
self.user_data = {
'username': 'testuser',
'cn': 'Test User',
'mail': 'test@example.com'
}
def test_user_creation(self):
"""Test user creation with valid data."""
user = TestUser(**self.user_data)
self.assertEqual(user.username, 'testuser')
self.assertEqual(user.cn, 'Test User')
self.assertEqual(user.mail, 'test@example.com')
def test_user_validation(self):
"""Test user validation."""
# Test invalid email
invalid_data = self.user_data.copy()
invalid_data['mail'] = 'invalid-email'
user = TestUser(**invalid_data)
with self.assertRaises(ValidationError):
user.full_clean()
@patch('ldaporm.models.get_ldap_connection')
def test_user_save(self, mock_connection):
"""Test user save to LDAP."""
mock_conn = Mock()
mock_connection.return_value = mock_conn
user = TestUser(**self.user_data)
user.save()
# Verify LDAP add was called
mock_conn.add_s.assert_called_once()
def test_binary_field_handling(self):
"""Test binary field handling."""
user = TestUser(**self.user_data)
user.photo = b'test_photo_data'
# Test serialization
self.assertEqual(user.photo, b'test_photo_data')
# Test null handling
user.photo = None
self.assertIsNone(user.photo)
This testing guide provides a comprehensive overview of how to author and run tests for the django-ldaporm project, ensuring code quality and reliability.