Baby Tracker Testing Guide

A comprehensive guide for testing the Baby Tracker application

Testing Guide

Concrete examples and best practices for testing features in the Baby Tracker application

Testing Framework

Baby Tracker uses Django's built-in testing framework along with Django REST Framework's testing utilities:

  • django.test.TestCase: Base class for Django tests
  • rest_framework.test.APIClient: Client for making API requests in tests
  • unittest.mock: For mocking external dependencies

Test Structure

Tests are organized by feature/model and placed in the appropriate app's tests directory:

  • tracker/tests/: Tests for the tracker app
  • recipes/tests/: Tests for the recipes app

Each test file focuses on a specific model or feature, with class names following the pattern {Feature}APITestCase.

๐Ÿงช Example: Adding a New Feature with Tests

Step 1: Define the Model

# tracker/models.py
from django.db import models
from django.conf import settings

class Medication(models.Model):
"""Model for tracking baby medications."""
baby = models.ForeignKey('Baby', on_delete=models.CASCADE, related_name='medications')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
dosage = models.CharField(max_length=50)
time = models.DateTimeField()
notes = models.TextField(blank=True, null=True)

def __str__(self):
return f"{self.name} - {self.dosage} - {self.time}"

Step 2: Create the Serializer

# tracker/serializers.py
from rest_framework import serializers
from .models import Medication

class MedicationSerializer(serializers.ModelSerializer):
class Meta:
model = Medication
fields = ['id', 'baby', 'name', 'dosage', 'time', 'notes']
read_only_fields = ['user']

Step 3: Implement the View

# tracker/views.py
from rest_framework import generics
from .models import Medication
from .serializers import MedicationSerializer
from .permissions import IsTenantUser

class MedicationListCreateView(generics.ListCreateAPIView):
serializer_class = MedicationSerializer
permission_classes = [IsTenantUser]

def get_queryset(self):
return Medication.objects.filter(user=self.request.user)

def perform_create(self, serializer):
serializer.save(user=self.request.user)

Step 4: Add URL Pattern

# tracker/urls.py
from django.urls import path
from .views import MedicationListCreateView

urlpatterns = [
# ... other URL patterns
path('medications/', MedicationListCreateView.as_view(), name='medication-list'),
]

Step 5: Write Tests

# tracker/tests/test_medication_api.py
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from tracker.models import Baby, Medication
from datetime import datetime

User = get_user_model()

class MedicationAPITestCase(TestCase):
def setUp(self):
# Create test users
self.user1 = User.objects.create_user(
username='testuser1',
email='test1@example.com',
password='testpassword1'
)

# Create test babies
self.baby1 = Baby.objects.create(
name='Baby One',
birth_date='2023-01-01',
gender='Male',
user=self.user1
)

# Setup API clients
self.client1 = APIClient()
self.client1.force_authenticate(user=self.user1)

def test_list_medications(self):
"""Test listing medications for authenticated user"""
url = reverse('medication-list')
response = self.client1.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)

# Test for proper permission handling
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data[0]['name'], 'Tylenol')

๐Ÿค– Testing Complex Features: AI Insights Example

For complex features like AI insights, you'll need to test both the underlying logic and the API endpoints. Here's how we test the AI insights feature:

Testing AI Insights API

# tracker/tests/test_ai_insights_api.py
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from tracker.models import Baby, Feeding
from datetime import datetime, timedelta

User = get_user_model()

class AIInsightsAPITestCase(TestCase):
def setUp(self):
# Create test users and babies
self.user1 = User.objects.create_user(
username='testuser1',
email='test1@example.com',
password='testpassword1'
)
self.baby1 = Baby.objects.create(
name='Baby One',
birth_date='2023-01-01',
gender='Male',
user=self.user1
)

# Create test feeding data for AI analysis
base_time = datetime.now()
for i in range(10):
Feeding.objects.create(
baby=self.baby1,
user=self.user1,
start_time=base_time - timedelta(hours=i*3),
feeding_type='bottle',
quantity=120,
unit='ml'
)

def test_feeding_insights_endpoint(self):
"""Test feeding insights endpoint"""
url = reverse('baby-ai-insights', kwargs={'pk': self.baby1.id}) + '?type=feeding'
response = self.client1.get(url)
# Verify successful response and data structure
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('feeding_insights', response.data)

Common Testing Challenges & Solutions

1. Timezone Issues in Tests

When working with datetime objects, timezone issues can cause test failures. Use UTC consistently and be aware of timezone conversions.

Example Fix: When converting datetime objects to numpy arrays, use timestamps instead of np.datetime64 to avoid timezone warnings:
# When converting datetime objects to numpy arrays, use timestamps instead:
times = sorted(df["time"].tolist())
times_utc = [t.timestamp() for t in times] # Convert to UTC timestamps
times_np = np.array(times_utc) # Now use numpy array operations
2. Permission Testing

Ensure you test both successful access by owners and denied access by non-owners.

Example Fix: Make sure your test assertions match the actual API behavior:
# If the API returns 400 BAD REQUEST for invalid data rather than 403 FORBIDDEN:
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) # Not 403
3. URL Patterns in Tests

Always use Django's reverse() function to generate URLs instead of hardcoding them.

Example Fix: Replace hardcoded URLs with reverse():
# Instead of:
url = f'/api/growth-milestones/{self.milestone1.id}/'

# Use:
url = reverse('growth-milestone-detail', kwargs={'pk': self.milestone1.id})

Best Practices for Testing

๐Ÿ“‹ Test Organization

  • Isolation: Each test should be independent
  • Realistic Data: Use realistic test data
  • Comprehensive Coverage: Test both success and failure cases
  • Clean Setup/Teardown: Properly set up and clean up test data

๐Ÿƒโ€โ™‚๏ธ Test Execution

  • Run Specific Tests: python manage.py test tracker.tests.test_baby_api
  • Run with Coverage: coverage run --source='.' manage.py test
  • View Coverage Report: coverage report
  • CI Integration: Tests run automatically on GitHub Actions