Testing Guide

Comprehensive guide to testing the ModestWear API.

Overview

ModestWear uses a multi-layered testing approach:

  • Unit Tests: Test individual functions and methods

  • Integration Tests: Test API endpoints and workflows

  • Manual Testing: Interactive testing via Swagger/ReDoc

  • Load Testing: Performance and scalability testing


Running Tests

All Tests

cd modestwear
python manage.py test

Specific App

python manage.py test apps.users
python manage.py test apps.catalog
python manage.py test apps.orders
python manage.py test apps.outfits

Specific Test Class

python manage.py test apps.users.tests.test_auth.AuthenticationTestCase

Specific Test Method

python manage.py test apps.users.tests.test_auth.AuthenticationTestCase.test_user_registration

With Coverage

# Install coverage
pip install coverage

# Run tests with coverage
coverage run --source='.' manage.py test

# View report
coverage report

# Generate HTML report
coverage html
# Open htmlcov/index.html in browser

Test Structure

Directory Layout

modestwear/
├── apps/
│   ├── users/
│   │   ├── tests/
│   │   │   ├── __init__.py
│   │   │   ├── test_models.py
│   │   │   ├── test_auth.py
│   │   │   ├── test_verification.py
│   │   │   └── test_social_auth.py
│   ├── catalog/
│   │   ├── tests/
│   │   │   ├── test_models.py
│   │   │   ├── test_views.py
│   │   │   └── test_filters.py
│   ├── orders/
│   │   ├── tests/
│   │   │   ├── test_cart.py
│   │   │   ├── test_orders.py
│   │   │   └── test_wishlist.py
│   └── outfits/
│       ├── tests/
│       │   ├── test_models.py
│       │   └── test_views.py

Unit Tests

Model Tests

from django.test import TestCase
from apps.users.models import User

class UserModelTestCase(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            email='test@example.com',
            password='TestPass123!',
            username='testuser'
        )
    
    def test_user_creation(self):
        """Test user is created correctly"""
        self.assertEqual(self.user.email, 'test@example.com')
        self.assertTrue(self.user.check_password('TestPass123!'))
        self.assertFalse(self.user.is_verified)
    
    def test_user_str_representation(self):
        """Test string representation"""
        self.assertEqual(str(self.user), 'test@example.com')
    
    def test_email_uniqueness(self):
        """Test email must be unique"""
        with self.assertRaises(Exception):
            User.objects.create_user(
                email='test@example.com',
                password='AnotherPass123!',
                username='testuser2'
            )

Serializer Tests

from django.test import TestCase
from apps.catalog.models import Product, Category
from apps.catalog.serializers import ProductSerializer

class ProductSerializerTestCase(TestCase):
    def setUp(self):
        self.category = Category.objects.create(
            name='Dresses',
            slug='dresses'
        )
        self.product_data = {
            'name': 'Test Dress',
            'slug': 'test-dress',
            'category': self.category.id,
            'base_price': '1299.99',
            'description': 'Test description'
        }
    
    def test_serializer_with_valid_data(self):
        """Test serializer with valid data"""
        serializer = ProductSerializer(data=self.product_data)
        self.assertTrue(serializer.is_valid())
    
    def test_serializer_with_invalid_price(self):
        """Test serializer rejects negative price"""
        self.product_data['base_price'] = '-100.00'
        serializer = ProductSerializer(data=self.product_data)
        self.assertFalse(serializer.is_valid())
        self.assertIn('base_price', serializer.errors)

Integration Tests

API Endpoint Tests

from rest_framework.test import APITestCase
from rest_framework import status
from django.urls import reverse
from apps.users.models import User

class AuthenticationAPITestCase(APITestCase):
    def setUp(self):
        self.register_url = reverse('user-register')
        self.login_url = reverse('user-login')
        self.user_data = {
            'email': 'test@example.com',
            'password': 'TestPass123!',
            'full_name': 'Test User'
        }
    
    def test_user_registration(self):
        """Test user can register"""
        response = self.client.post(
            self.register_url,
            self.user_data,
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertTrue(response.data['success'])
        self.assertIn('tokens', response.data['data'])
        self.assertIn('user', response.data['data'])
    
    def test_user_login(self):
        """Test user can login"""
        # Create user first
        User.objects.create_user(
            email=self.user_data['email'],
            password=self.user_data['password'],
            username='testuser'
        )
        
        # Attempt login
        response = self.client.post(
            self.login_url,
            {
                'email': self.user_data['email'],
                'password': self.user_data['password']
            },
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertIn('tokens', response.data['data'])
    
    def test_login_with_invalid_credentials(self):
        """Test login fails with wrong password"""
        User.objects.create_user(
            email=self.user_data['email'],
            password=self.user_data['password'],
            username='testuser'
        )
        
        response = self.client.post(
            self.login_url,
            {
                'email': self.user_data['email'],
                'password': 'WrongPassword123!'
            },
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

Authenticated Endpoint Tests

class CartAPITestCase(APITestCase):
    def setUp(self):
        # Create user
        self.user = User.objects.create_user(
            email='test@example.com',
            password='TestPass123!',
            username='testuser'
        )
        
        # Get tokens
        response = self.client.post(
            reverse('user-login'),
            {'email': 'test@example.com', 'password': 'TestPass123!'},
            format='json'
        )
        self.access_token = response.data['data']['tokens']['access_token']
        
        # Set authentication
        self.client.credentials(
            HTTP_AUTHORIZATION=f'Bearer {self.access_token}'
        )
        
        # Create test product
        self.category = Category.objects.create(name='Test', slug='test')
        self.product = Product.objects.create(
            name='Test Product',
            slug='test-product',
            category=self.category,
            base_price='999.99'
        )
        self.variant = ProductVariant.objects.create(
            product=self.product,
            sku='TEST-001',
            size=2,  # M
            color='Black',
            stock_available=10
        )
    
    def test_add_to_cart(self):
        """Test adding item to cart"""
        response = self.client.post(
            reverse('cart-add'),
            {'variant_id': self.variant.id, 'quantity': 2},
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertTrue(response.data['success'])
    
    def test_view_cart(self):
        """Test viewing cart contents"""
        # Add item first
        CartItem.objects.create(
            user=self.user,
            variant=self.variant,
            quantity=2
        )
        
        response = self.client.get(reverse('cart-view'))
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data['items']), 1)
        self.assertEqual(response.data['summary']['total_items'], 2)

Workflow Tests

Complete E-Commerce Flow

class ECommerceWorkflowTestCase(APITestCase):
    def test_complete_purchase_flow(self):
        """Test complete flow: register → browse → cart → order"""
        
        # 1. Register user
        register_response = self.client.post(
            reverse('user-register'),
            {
                'email': 'customer@example.com',
                'password': 'SecurePass123!',
                'full_name': 'Test Customer'
            },
            format='json'
        )
        self.assertEqual(register_response.status_code, 201)
        access_token = register_response.data['data']['tokens']['access_token']
        
        # 2. Browse products
        products_response = self.client.get(reverse('product-list'))
        self.assertEqual(products_response.status_code, 200)
        
        # 3. Add to cart
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {access_token}')
        cart_response = self.client.post(
            reverse('cart-add'),
            {'variant_id': 1, 'quantity': 2},
            format='json'
        )
        self.assertEqual(cart_response.status_code, 201)
        
        # 4. Create order
        order_response = self.client.post(
            reverse('order-create'),
            {
                'address': '123 Test St, Cape Town, South Africa',
                'payment_method': 'stripe'
            },
            format='json'
        )
        self.assertEqual(order_response.status_code, 201)
        self.assertIn('order_id', order_response.data['data'])

Manual Testing

Using Swagger UI

  1. Navigate to: https://modestwear.onrender.com/docs/

  2. Test Authentication:

    • Click on /api/users/register/

    • Click “Try it out”

    • Enter test data

    • Click “Execute”

    • Copy access token from response

  3. Authorize:

    • Click “Authorize” button at top

    • Enter: Bearer <your_access_token>

    • Click “Authorize”

  4. Test Endpoints:

    • All authenticated endpoints now work

    • Try adding to cart, creating orders, etc.

Using cURL

# Register
curl -X POST https://modestwear.onrender.com/api/users/register/ \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "TestPass123!",
    "full_name": "Test User"
  }'

# Login
curl -X POST https://modestwear.onrender.com/api/users/login/ \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "TestPass123!"
  }'

# Save token
TOKEN="<access_token_from_login>"

# Browse products
curl https://modestwear.onrender.com/api/catalog/products/

# Add to cart (authenticated)
curl -X POST https://modestwear.onrender.com/api/orders/cart/add/ \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "variant_id": 1,
    "quantity": 2
  }'

Using Postman

  1. Import Collection:

    • Create new collection “ModestWear API”

    • Add base URL variable: {{base_url}} = https://modestwear.onrender.com

  2. Setup Authentication:

    • Create “Register” request

    • Create “Login” request

    • Add test script to save token:

    pm.test("Login successful", function () {
        var jsonData = pm.response.json();
        pm.environment.set("access_token", jsonData.data.tokens.access_token);
    });
    
  3. Use Token:

    • In collection settings, add Authorization

    • Type: Bearer Token

    • Token: {{access_token}}


Load Testing

Using Locust

Install:

pip install locust

Create locustfile.py:

from locust import HttpUser, task, between

class ModestWearUser(HttpUser):
    wait_time = between(1, 3)
    
    def on_start(self):
        """Login and get token"""
        response = self.client.post("/api/users/login/", json={
            "email": "test@example.com",
            "password": "TestPass123!"
        })
        self.token = response.json()['data']['tokens']['access_token']
        self.client.headers.update({
            'Authorization': f'Bearer {self.token}'
        })
    
    @task(3)
    def browse_products(self):
        """Browse products (most common action)"""
        self.client.get("/api/catalog/products/")
    
    @task(2)
    def view_product(self):
        """View product details"""
        self.client.get("/api/catalog/products/1/")
    
    @task(1)
    def view_cart(self):
        """View cart"""
        self.client.get("/api/orders/cart/")
    
    @task(1)
    def add_to_cart(self):
        """Add item to cart"""
        self.client.post("/api/orders/cart/add/", json={
            "variant_id": 1,
            "quantity": 1
        })

Run Load Test:

locust -f locustfile.py --host=https://modestwear.onrender.com

Access UI: http://localhost:8089

Test Scenarios:

  • Light Load: 10 users, 1 user/second spawn rate

  • Medium Load: 50 users, 5 users/second spawn rate

  • Heavy Load: 100 users, 10 users/second spawn rate


Test Data

Fixtures

Create fixture:

python manage.py dumpdata catalog.Category catalog.Product > fixtures/products.json

Load fixture:

python manage.py loaddata fixtures/products.json

Factory Pattern

import factory
from apps.users.models import User

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User
    
    email = factory.Sequence(lambda n: f'user{n}@example.com')
    username = factory.Sequence(lambda n: f'user{n}')
    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')
    is_verified = True

# Usage in tests
user = UserFactory.create()
users = UserFactory.create_batch(10)

Continuous Integration

GitHub Actions

.github/workflows/tests.yml:

name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: 3.11
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt
    
    - name: Run tests
      env:
        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
        SECRET_KEY: test-secret-key
      run: |
        python modestwear/manage.py test
    
    - name: Upload coverage
      run: |
        coverage run --source='.' modestwear/manage.py test
        coverage report

Test Coverage Goals

Target Coverage

  • Overall: 80%+

  • Models: 90%+

  • Views/APIs: 85%+

  • Serializers: 80%+

  • Utilities: 75%+

Check Coverage

coverage report --fail-under=80

Best Practices

  1. Test Isolation: Each test should be independent

  2. Clear Names: Test names describe what they test

  3. Arrange-Act-Assert: Structure tests clearly

  4. Mock External Services: Don’t call real APIs in tests

  5. Test Edge Cases: Not just happy paths

  6. Fast Tests: Keep tests fast (< 1 second each)

  7. Continuous Testing: Run tests on every commit


Next Steps