Follow TDD Approach

This document outlines a practical Test-Driven Development (TDD) workflow for full-stack applications using Django (backend) and React (frontend), including:

  1. 🧭 The TDD Workflow

  2. 📁 Folder Structures

  3. ✅ Test Types

  4. ✍️ Example Test Cases


🧭 TDD Workflow: Red – Green – Refactor

Step

Action

🔴 1

Write a failing test

🟢 2

Write the minimum code to pass the test

♻️ 3

Refactor the code while keeping the test green

🔁 4

Repeat for each new behavior


📁 Folder Structure

Django (Backend)

/wsgi/
├── bodhitree/
|   ├── bodhitree_tests/
|   |   ├──__init__.py
|   |   ├──performance/
|   |   ├──integration/
|   |   ├──e2e/
|   |   ├──security/
│   ├── myapp/
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── views.py
│   │   ├── urls.py
│   │   ├── tests/
│   │   │   ├── __init__.py
│   │   │   ├── test_models.py
│   │   │   ├── test_serializers.py
│   │   │   ├── test_views.py
│   │   │   └── test_urls.py

React (Frontend)

/frontend/
├── src/
│   ├── components/
│   │   ├── Button.jsx
│   │   └── __tests__/
│   │       └── Button.test.jsx
│   ├── pages/
│   │   ├── Register.jsx
│   │   └── __tests__/
│   │       └── Register.test.jsx
│   ├── hooks/
│   │   ├── useAuth.js
│   │   └── __tests__/
│   │       └── useAuth.test.js
│   ├── utils/
│   │   ├── api.js
│   │   └── api.test.js
│   ├── setupTests.js

✅ Test Types with Logic and Examples

1. Unit Testing

Django:

def test_discount_calculation():
    product = Product(price=100)
    assert product.get_discounted_price(10) == 90

React:

test('calls onClick when clicked', () => {
  const onClick = jest.fn();
  render(<Button onClick={onClick} label="Save" />);
  fireEvent.click(screen.getByText('Save'));
  expect(onClick).toHaveBeenCalled();
});

2. Integration Testing

Django:

def test_create_user_success():
    response = self.client.post('/api/register/', {
        "email": "test@example.com", "password": "pass123"
    })
    assert response.status_code == 201

React:

test('submits form and shows success message', async () => {
  render(<Register />);
  fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'test@example.com' } });
  fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'pass123' } });
  fireEvent.click(screen.getByText(/Register/));
  expect(await screen.findByText(/Welcome/i)).toBeInTheDocument();
});

3. Functional Testing

Django:

def test_non_admin_cannot_delete_user():
    self.client.login(username="user", password="pass")
    response = self.client.delete("/api/users/1/")
    assert response.status_code == 403

React:

test('sets user as authenticated after login', async () => {
  mockLoginAPI.mockResolvedValue({ token: 'abc', user: { id: 1, email: 'test@example.com' } });
  const { result } = renderHook(() => useAuth(), { wrapper: AuthProvider });
  await act(() => result.current.login('test@example.com', 'pass123'));
  expect(result.current.user.email).toBe('test@example.com');
});

4. End-to-End (E2E) Testing

Playwright:

import { test, expect } from '@playwright/test';

test('User Registration', async ({ page }) => {
  await page.goto('/register');
  await page.fill('input[name="email"]', 'user@example.com');
  await page.fill('input[name="password"]', 'pass123');
  await page.click('button:has-text("Register")');
  await expect(page.getByText('Welcome')).toBeVisible();
});

5. Smoke Testing

Django:

curl http://localhost:8000/health/
# Response: {"status": "ok"}

React:

test('renders homepage', () => {
  render(<Home />);
  expect(screen.getByText(/Welcome to/)).toBeInTheDocument();
});

6. Regression Testing

Run all previously passing tests on each code change to ensure stability.


🧪 Real TDD Example: “Create a Course”

Django

Test First:

def test_create_course(self):
    response = self.client.post('/api/courses/', {
        "title": "Python Basics", "duration": "3 months"
    })
    self.assertEqual(response.status_code, 201)

Minimal Implementation:

@api_view(['POST'])
def create_course(request):
    course = Course.objects.create(**request.data)
    return Response(status=201)

Refactor:

  • Add CourseSerializer

  • Add validation rules

  • Unit test model and serializer


React

Test First:

test('shows success message on course creation', async () => {
  render(<CreateCourse />);
  fireEvent.change(screen.getByLabelText(/Title/), { target: { value: 'Python Basics' } });
  fireEvent.change(screen.getByLabelText(/Duration/), { target: { value: '3 months' } });
  fireEvent.click(screen.getByText('Submit'));
  expect(await screen.findByText(/Course Created/)).toBeInTheDocument();
});

Minimal Implementation:

const handleSubmit = async () => {
  await axios.post('/api/courses', { title, duration });
  setMessage("Course Created");
};

Refactor:

  • Move logic to api.js

  • Add validations

  • Improve UX and form handling


🚀 TDD Best Practices

Tip

Why

Write small, testable units

Easier to debug

Use fixtures or factories

Avoid redundant setup

Automate tests in CI/CD

Faster feedback

Mock external services

Prevent failures from dependencies

Use meaningful test names

Improve clarity


✅ This TDD structure ensures high test coverage, better design, and early bug detection in both Django and React applications.