Follow TDD Approach
This document outlines a practical Test-Driven Development (TDD) workflow for full-stack applications using Django (backend) and React (frontend), including:
🧭 The TDD Workflow
📁 Folder Structures
✅ Test Types
✍️ 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.jsAdd 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.