or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

auth-permissions.mdcontent-negotiation.mddecorators.mdfields-validation.mdgeneric-views.mdindex.mdpagination-filtering.mdrequest-response.mdrouters-urls.mdserializers.mdstatus-exceptions.mdtesting.mdviews-viewsets.md

testing.mddocs/

0

# Testing Utilities

1

2

Comprehensive testing tools including API client, request factory, and test case classes for thorough API testing in Django REST Framework.

3

4

## Capabilities

5

6

### API Request Factory

7

8

Factory for creating mock API requests for testing.

9

10

```python { .api }

11

class APIRequestFactory(DjangoRequestFactory):

12

"""

13

Factory for creating API requests without going through Django's WSGI interface.

14

"""

15

def __init__(self, enforce_csrf_checks=False, **defaults):

16

"""

17

Args:

18

enforce_csrf_checks (bool): Enable CSRF checking

19

**defaults: Default request parameters

20

"""

21

self.enforce_csrf_checks = enforce_csrf_checks

22

super().__init__(**defaults)

23

24

def _encode_data(self, data, content_type):

25

"""

26

Encode data for request body based on content type.

27

28

Args:

29

data: Data to encode

30

content_type (str): Content type for encoding

31

32

Returns:

33

tuple: (encoded_data, content_type)

34

"""

35

36

def get(self, path, data=None, **extra):

37

"""

38

Create GET request.

39

40

Args:

41

path (str): URL path

42

data (dict): Query parameters

43

**extra: Additional request parameters

44

45

Returns:

46

Request: Mock GET request

47

"""

48

49

def post(self, path, data=None, format=None, content_type=None, **extra):

50

"""

51

Create POST request.

52

53

Args:

54

path (str): URL path

55

data: Request body data

56

format (str): Data format ('json', 'multipart', etc.)

57

content_type (str): Custom content type

58

**extra: Additional request parameters

59

60

Returns:

61

Request: Mock POST request

62

"""

63

64

def put(self, path, data=None, format=None, content_type=None, **extra):

65

"""Create PUT request."""

66

67

def patch(self, path, data=None, format=None, content_type=None, **extra):

68

"""Create PATCH request."""

69

70

def delete(self, path, data=None, format=None, content_type=None, **extra):

71

"""Create DELETE request."""

72

73

def options(self, path, data=None, format=None, content_type=None, **extra):

74

"""Create OPTIONS request."""

75

76

def head(self, path, data=None, **extra):

77

"""Create HEAD request."""

78

79

def trace(self, path, **extra):

80

"""Create TRACE request."""

81

```

82

83

### API Test Client

84

85

Enhanced test client with API-specific functionality.

86

87

```python { .api }

88

class APIClient(APIRequestFactory, DjangoClient):

89

"""

90

Test client for making API requests in tests.

91

"""

92

def __init__(self, enforce_csrf_checks=False, **defaults):

93

"""

94

Args:

95

enforce_csrf_checks (bool): Enable CSRF checking

96

**defaults: Default client parameters

97

"""

98

super().__init__(enforce_csrf_checks=enforce_csrf_checks, **defaults)

99

self._credentials = {}

100

101

def credentials(self, **kwargs):

102

"""

103

Set authentication credentials for subsequent requests.

104

105

Args:

106

**kwargs: Authentication headers (HTTP_AUTHORIZATION, etc.)

107

"""

108

self._credentials = kwargs

109

110

def force_authenticate(self, user=None, token=None):

111

"""

112

Force authentication for subsequent requests.

113

114

Args:

115

user: User instance to authenticate as

116

token: Authentication token

117

"""

118

self.handler._force_user = user

119

self.handler._force_token = token

120

121

def logout(self):

122

"""

123

Remove authentication and credentials.

124

"""

125

self._credentials = {}

126

if hasattr(self.handler, '_force_user'):

127

del self.handler._force_user

128

if hasattr(self.handler, '_force_token'):

129

del self.handler._force_token

130

super().logout()

131

```

132

133

### Test Case Classes

134

135

Specialized test case classes for API testing.

136

137

```python { .api }

138

class APITestCase(testcases.TestCase):

139

"""

140

Test case class with APIClient and additional API testing utilities.

141

"""

142

client_class = APIClient

143

144

def setUp(self):

145

"""Set up test case with API client."""

146

super().setUp()

147

self.client = self.client_class()

148

149

def _pre_setup(self):

150

"""Pre-setup hook for test case."""

151

super()._pre_setup()

152

153

def _post_teardown(self):

154

"""Post-teardown hook for test case."""

155

super()._post_teardown()

156

157

class APITransactionTestCase(testcases.TransactionTestCase):

158

"""

159

Transaction test case for API testing with database transactions.

160

"""

161

client_class = APIClient

162

163

def _pre_setup(self):

164

"""Pre-setup with transaction handling."""

165

super()._pre_setup()

166

self.client = self.client_class()

167

168

class APISimpleTestCase(testcases.SimpleTestCase):

169

"""

170

Simple test case for API testing without database.

171

"""

172

client_class = APIClient

173

174

def setUp(self):

175

"""Set up simple test case."""

176

super().setUp()

177

self.client = self.client_class()

178

179

class APILiveServerTestCase(testcases.LiveServerTestCase):

180

"""

181

Live server test case for API integration testing.

182

"""

183

client_class = APIClient

184

185

def setUp(self):

186

"""Set up live server test case."""

187

super().setUp()

188

self.client = self.client_class()

189

190

class URLPatternsTestCase(testcases.SimpleTestCase):

191

"""

192

Test case for testing URL patterns and routing.

193

"""

194

def setUp(self):

195

"""Set up URL patterns test case."""

196

super().setUp()

197

self.client = APIClient()

198

```

199

200

### Test Utilities

201

202

Utility functions for API testing.

203

204

```python { .api }

205

def force_authenticate(request, user=None, token=None):

206

"""

207

Force authentication on a request object for testing.

208

209

Args:

210

request: Request object to authenticate

211

user: User instance to authenticate as

212

token: Authentication token

213

"""

214

request.user = user or AnonymousUser()

215

request.auth = token

216

```

217

218

## Usage Examples

219

220

### Basic API Testing

221

222

```python

223

from rest_framework.test import APITestCase, APIClient

224

from rest_framework import status

225

from django.contrib.auth.models import User

226

from myapp.models import Book

227

228

class BookAPITestCase(APITestCase):

229

def setUp(self):

230

"""Set up test data."""

231

self.user = User.objects.create_user(

232

username='testuser',

233

email='test@example.com',

234

password='testpass123'

235

)

236

self.book = Book.objects.create(

237

title='Test Book',

238

author='Test Author',

239

isbn='1234567890123'

240

)

241

242

def test_get_book_list(self):

243

"""Test retrieving book list."""

244

url = '/api/books/'

245

response = self.client.get(url)

246

247

self.assertEqual(response.status_code, status.HTTP_200_OK)

248

self.assertEqual(len(response.data), 1)

249

self.assertEqual(response.data[0]['title'], 'Test Book')

250

251

def test_create_book_authenticated(self):

252

"""Test creating book with authentication."""

253

self.client.force_authenticate(user=self.user)

254

255

url = '/api/books/'

256

data = {

257

'title': 'New Book',

258

'author': 'New Author',

259

'isbn': '9876543210987'

260

}

261

response = self.client.post(url, data, format='json')

262

263

self.assertEqual(response.status_code, status.HTTP_201_CREATED)

264

self.assertEqual(Book.objects.count(), 2)

265

self.assertEqual(response.data['title'], 'New Book')

266

267

def test_create_book_unauthenticated(self):

268

"""Test creating book without authentication fails."""

269

url = '/api/books/'

270

data = {'title': 'Unauthorized Book'}

271

response = self.client.post(url, data, format='json')

272

273

self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

274

```

275

276

### Token Authentication Testing

277

278

```python

279

from rest_framework.authtoken.models import Token

280

281

class TokenAuthTestCase(APITestCase):

282

def setUp(self):

283

self.user = User.objects.create_user(

284

username='testuser',

285

password='testpass123'

286

)

287

self.token = Token.objects.create(user=self.user)

288

289

def test_token_authentication(self):

290

"""Test API access with token authentication."""

291

# Set token in Authorization header

292

self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token.key)

293

294

url = '/api/protected-endpoint/'

295

response = self.client.get(url)

296

297

self.assertEqual(response.status_code, status.HTTP_200_OK)

298

299

def test_invalid_token(self):

300

"""Test API access with invalid token."""

301

self.client.credentials(HTTP_AUTHORIZATION='Token invalid-token')

302

303

url = '/api/protected-endpoint/'

304

response = self.client.get(url)

305

306

self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

307

308

def test_no_token(self):

309

"""Test API access without token."""

310

url = '/api/protected-endpoint/'

311

response = self.client.get(url)

312

313

self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

314

```

315

316

### Testing Different Content Types

317

318

```python

319

class ContentTypeTestCase(APITestCase):

320

def test_json_request(self):

321

"""Test JSON content type."""

322

url = '/api/books/'

323

data = {'title': 'JSON Book', 'author': 'JSON Author'}

324

response = self.client.post(url, data, format='json')

325

326

self.assertEqual(response.status_code, status.HTTP_201_CREATED)

327

328

def test_form_request(self):

329

"""Test form-encoded content type."""

330

url = '/api/books/'

331

data = {'title': 'Form Book', 'author': 'Form Author'}

332

response = self.client.post(url, data) # Default is form-encoded

333

334

self.assertEqual(response.status_code, status.HTTP_201_CREATED)

335

336

def test_multipart_request(self):

337

"""Test multipart content type with file upload."""

338

url = '/api/books/'

339

with open('test_cover.jpg', 'rb') as cover_file:

340

data = {

341

'title': 'Book with Cover',

342

'author': 'Cover Author',

343

'cover_image': cover_file

344

}

345

response = self.client.post(url, data, format='multipart')

346

347

self.assertEqual(response.status_code, status.HTTP_201_CREATED)

348

```

349

350

### Testing Custom Headers and Parameters

351

352

```python

353

class CustomHeaderTestCase(APITestCase):

354

def test_custom_header(self):

355

"""Test API with custom headers."""

356

url = '/api/books/'

357

response = self.client.get(

358

url,

359

HTTP_X_CUSTOM_HEADER='custom-value',

360

HTTP_ACCEPT='application/json'

361

)

362

363

self.assertEqual(response.status_code, status.HTTP_200_OK)

364

365

def test_query_parameters(self):

366

"""Test API with query parameters."""

367

url = '/api/books/'

368

response = self.client.get(url, {

369

'search': 'django',

370

'ordering': 'title',

371

'page': 1

372

})

373

374

self.assertEqual(response.status_code, status.HTTP_200_OK)

375

```

376

377

### Testing Pagination

378

379

```python

380

class PaginationTestCase(APITestCase):

381

def setUp(self):

382

# Create multiple books for pagination testing

383

for i in range(25):

384

Book.objects.create(

385

title=f'Book {i}',

386

author=f'Author {i}',

387

isbn=f'123456789012{i}'

388

)

389

390

def test_pagination_first_page(self):

391

"""Test first page of paginated results."""

392

url = '/api/books/'

393

response = self.client.get(url)

394

395

self.assertEqual(response.status_code, status.HTTP_200_OK)

396

self.assertIn('results', response.data)

397

self.assertIn('next', response.data)

398

self.assertIsNone(response.data['previous'])

399

self.assertEqual(len(response.data['results']), 20) # Default page size

400

401

def test_pagination_custom_page_size(self):

402

"""Test custom page size."""

403

url = '/api/books/?page_size=10'

404

response = self.client.get(url)

405

406

self.assertEqual(response.status_code, status.HTTP_200_OK)

407

self.assertEqual(len(response.data['results']), 10)

408

```

409

410

### Testing Error Handling

411

412

```python

413

class ErrorHandlingTestCase(APITestCase):

414

def test_validation_error(self):

415

"""Test validation error responses."""

416

url = '/api/books/'

417

data = {'title': ''} # Invalid: empty title

418

response = self.client.post(url, data, format='json')

419

420

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

421

self.assertIn('title', response.data)

422

423

def test_not_found_error(self):

424

"""Test 404 error for non-existent resource."""

425

url = '/api/books/999/' # Non-existent book ID

426

response = self.client.get(url)

427

428

self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

429

430

def test_method_not_allowed(self):

431

"""Test 405 error for unsupported method."""

432

url = '/api/books/1/'

433

response = self.client.patch(url, {'title': 'Updated'}, format='json')

434

435

# Assuming PATCH is not allowed on this endpoint

436

self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)

437

```

438

439

### Unit Testing Views Directly

440

441

```python

442

from rest_framework.test import APIRequestFactory

443

from myapp.views import BookViewSet

444

445

class ViewUnitTestCase(APITestCase):

446

def setUp(self):

447

self.factory = APIRequestFactory()

448

self.user = User.objects.create_user(

449

username='testuser',

450

password='testpass123'

451

)

452

453

def test_viewset_list_action(self):

454

"""Test viewset list action directly."""

455

# Create request

456

request = self.factory.get('/api/books/')

457

request.user = self.user

458

459

# Test view directly

460

view = BookViewSet.as_view({'get': 'list'})

461

response = view(request)

462

463

self.assertEqual(response.status_code, status.HTTP_200_OK)

464

465

def test_viewset_create_action(self):

466

"""Test viewset create action directly."""

467

data = {'title': 'Direct Test Book', 'author': 'Test Author'}

468

request = self.factory.post('/api/books/', data, format='json')

469

request.user = self.user

470

471

view = BookViewSet.as_view({'post': 'create'})

472

response = view(request)

473

474

self.assertEqual(response.status_code, status.HTTP_201_CREATED)

475

```

476

477

### Mocking External Services

478

479

```python

480

from unittest.mock import patch, Mock

481

482

class ExternalServiceTestCase(APITestCase):

483

@patch('myapp.services.external_api_call')

484

def test_with_mocked_service(self, mock_api_call):

485

"""Test API endpoint that calls external service."""

486

# Configure mock

487

mock_api_call.return_value = {'status': 'success', 'data': 'mocked'}

488

489

url = '/api/books/sync/'

490

response = self.client.post(url, format='json')

491

492

self.assertEqual(response.status_code, status.HTTP_200_OK)

493

mock_api_call.assert_called_once()

494

```