or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-parsing.mdfield-types.mdframework-parsers.mdindex.mdtesting-utilities.md

testing-utilities.mddocs/

0

# Testing Utilities

1

2

Testing support including base test classes for parser validation, common test scenarios, and utilities for testing request parsing across different web frameworks. The testing utilities provide a standardized approach to validating parser behavior and ensuring consistent functionality across framework implementations.

3

4

## Capabilities

5

6

### Common Test Case Base Class

7

8

Base test class that defines standard test methods for validating parser functionality across different web frameworks.

9

10

```python { .api }

11

class CommonTestCase:

12

"""

13

Base test class for validating parser functionality.

14

15

Provides common test methods that should work across all framework parsers.

16

Subclasses must implement create_app() to return a WSGI-compatible application.

17

18

Methods:

19

create_app(): Abstract method to create test application

20

create_testapp(app): Create webtest.TestApp wrapper

21

before_create_app(): Hook called before app creation

22

after_create_app(): Hook called after app creation

23

"""

24

25

def create_app(self):

26

"""

27

Create and return a WSGI-compatible test application.

28

29

Must be implemented by subclasses to create framework-specific test app

30

with routes for testing parser functionality.

31

32

Returns:

33

WSGI application: Test application with parser routes

34

35

Raises:

36

NotImplementedError: If not implemented by subclass

37

"""

38

raise NotImplementedError("Must define create_app()")

39

40

def create_testapp(self, app):

41

"""

42

Create webtest.TestApp wrapper for testing.

43

44

Args:

45

app: WSGI application to wrap

46

47

Returns:

48

webtest.TestApp: Test client for making requests

49

"""

50

return webtest.TestApp(app)

51

52

def before_create_app(self):

53

"""Hook called before application creation. Override for setup."""

54

pass

55

56

def after_create_app(self):

57

"""Hook called after application creation. Override for teardown."""

58

pass

59

```

60

61

### Test Fixtures

62

63

Pytest fixtures for setting up test applications and clients.

64

65

```python { .api }

66

@pytest.fixture(scope="class")

67

def testapp(self):

68

"""

69

Pytest fixture that provides a test application client.

70

71

Manages the lifecycle of test application creation and cleanup.

72

Calls before_create_app(), creates the app, wraps it in TestApp,

73

and calls after_create_app() for cleanup.

74

75

Yields:

76

webtest.TestApp: Test client for making HTTP requests

77

"""

78

```

79

80

### Standard Test Methods

81

82

Common test methods that validate core parsing functionality.

83

84

```python { .api }

85

def test_parse_querystring_args(self, testapp):

86

"""

87

Test parsing arguments from query string parameters.

88

89

Validates that query parameters are correctly parsed and validated

90

according to the defined schema.

91

92

Expected test route: GET /echo?name=Fred -> {"name": "Fred"}

93

"""

94

95

def test_parse_form(self, testapp):

96

"""

97

Test parsing arguments from form data.

98

99

Validates that form-encoded data is correctly parsed and validated.

100

101

Expected test route: POST /echo_form with form data -> parsed JSON

102

"""

103

104

def test_parse_json(self, testapp):

105

"""

106

Test parsing arguments from JSON request body.

107

108

Validates that JSON request bodies are correctly parsed and validated.

109

110

Expected test route: POST /echo_json with JSON body -> parsed JSON

111

"""

112

```

113

114

## Usage Examples

115

116

### Creating Framework-Specific Test Cases

117

118

```python

119

import pytest

120

from webargs.testing import CommonTestCase

121

from webargs import fields

122

from webargs.flaskparser import use_args

123

from flask import Flask, jsonify

124

125

class TestFlaskParser(CommonTestCase):

126

"""Test case for Flask parser using CommonTestCase."""

127

128

def create_app(self):

129

"""Create Flask test application with parser routes."""

130

app = Flask(__name__)

131

132

@app.route("/echo")

133

@use_args({"name": fields.Str()}, location="query")

134

def echo(args):

135

return jsonify(args)

136

137

@app.route("/echo_form", methods=["POST"])

138

@use_args({"name": fields.Str()}, location="form")

139

def echo_form(args):

140

return jsonify(args)

141

142

@app.route("/echo_json", methods=["POST"])

143

@use_args({"name": fields.Str()}, location="json")

144

def echo_json(args):

145

return jsonify(args)

146

147

return app

148

149

def test_custom_validation(self, testapp):

150

"""Additional test specific to Flask implementation."""

151

response = testapp.get("/echo?name=TestUser")

152

assert response.json == {"name": "TestUser"}

153

```

154

155

### Django Test Case Example

156

157

```python

158

from django.conf import settings

159

from django.test import RequestFactory

160

from webargs.testing import CommonTestCase

161

from webargs.djangoparser import use_args

162

from webargs import fields

163

164

class TestDjangoParser(CommonTestCase):

165

"""Test case for Django parser."""

166

167

def before_create_app(self):

168

"""Configure Django settings before app creation."""

169

if not settings.configured:

170

settings.configure(

171

SECRET_KEY='test-key',

172

ROOT_URLCONF='test_urls',

173

)

174

175

def create_app(self):

176

"""Create Django test application."""

177

from django.http import JsonResponse

178

from django.urls import path

179

180

@use_args({"name": fields.Str()}, location="query")

181

def echo_view(request, args):

182

return JsonResponse(args)

183

184

# Return WSGI application

185

from django.core.wsgi import get_wsgi_application

186

return get_wsgi_application()

187

```

188

189

### Async Framework Testing

190

191

```python

192

import asyncio

193

from webargs.testing import CommonTestCase

194

from webargs.aiohttpparser import use_args

195

from webargs import fields

196

from aiohttp import web

197

198

class TestAIOHTTPParser(CommonTestCase):

199

"""Test case for aiohttp parser with async support."""

200

201

def create_app(self):

202

"""Create aiohttp test application."""

203

app = web.Application()

204

205

@use_args({"name": fields.Str()}, location="query")

206

async def echo_handler(request, args):

207

return web.json_response(args)

208

209

app.router.add_get("/echo", echo_handler)

210

return app

211

212

def create_testapp(self, app):

213

"""Create async-compatible test client."""

214

import webtest_aiohttp

215

return webtest_aiohttp.TestApp(app)

216

```

217

218

### Custom Validation Testing

219

220

```python

221

class TestCustomValidation(CommonTestCase):

222

"""Test custom validation scenarios."""

223

224

def create_app(self):

225

app = Flask(__name__)

226

227

def validate_positive(value):

228

if value <= 0:

229

raise ValidationError("Must be positive")

230

return True

231

232

@app.route("/validate")

233

@use_args({

234

"number": fields.Int(validate=validate_positive)

235

}, location="query")

236

def validate_endpoint(args):

237

return jsonify(args)

238

239

return app

240

241

def test_positive_validation(self, testapp):

242

"""Test custom positive number validation."""

243

# Valid positive number

244

response = testapp.get("/validate?number=5")

245

assert response.json == {"number": 5}

246

247

# Invalid negative number should return 422

248

response = testapp.get("/validate?number=-1", expect_errors=True)

249

assert response.status_code == 422

250

```

251

252

### Error Handling Testing

253

254

```python

255

class TestErrorHandling(CommonTestCase):

256

"""Test error handling scenarios."""

257

258

def create_app(self):

259

app = Flask(__name__)

260

261

@app.route("/required")

262

@use_args({"name": fields.Str(required=True)}, location="query")

263

def required_endpoint(args):

264

return jsonify(args)

265

266

return app

267

268

def test_missing_required_field(self, testapp):

269

"""Test handling of missing required fields."""

270

response = testapp.get("/required", expect_errors=True)

271

assert response.status_code == 422

272

assert "required" in response.json.get("messages", {}).get("query", {}).get("name", [])

273

274

def test_validation_error_structure(self, testapp):

275

"""Test structure of validation error responses."""

276

response = testapp.get("/required", expect_errors=True)

277

278

# Verify error message structure

279

assert "messages" in response.json

280

assert "query" in response.json["messages"]

281

assert isinstance(response.json["messages"]["query"], dict)

282

```

283

284

### Multi-Location Parsing Tests

285

286

```python

287

class TestMultiLocationParsing(CommonTestCase):

288

"""Test parsing from multiple request locations."""

289

290

def create_app(self):

291

app = Flask(__name__)

292

293

@app.route("/multi", methods=["POST"])

294

@use_args({

295

"name": fields.Str(location="json"),

296

"page": fields.Int(location="query", missing=1),

297

"token": fields.Str(location="headers", data_key="Authorization")

298

})

299

def multi_location_endpoint(args):

300

return jsonify(args)

301

302

return app

303

304

def test_multi_location_parsing(self, testapp):

305

"""Test parsing from JSON, query, and headers simultaneously."""

306

response = testapp.post_json(

307

"/multi?page=2",

308

{"name": "test"},

309

headers={"Authorization": "Bearer token123"}

310

)

311

312

expected = {

313

"name": "test",

314

"page": 2,

315

"token": "Bearer token123"

316

}

317

assert response.json == expected

318

```

319

320

## Test Utilities

321

322

### Helper Functions

323

324

```python { .api }

325

def create_test_request(framework, method="GET", path="/", **kwargs):

326

"""

327

Create framework-specific test request object.

328

329

Args:

330

framework (str): Framework name ("flask", "django", etc.)

331

method (str): HTTP method

332

path (str): Request path

333

**kwargs: Additional request parameters

334

335

Returns:

336

Request object appropriate for the framework

337

"""

338

339

def assert_validation_error(response, field_name, location="json"):

340

"""

341

Assert that response contains validation error for specific field.

342

343

Args:

344

response: Test response object

345

field_name (str): Name of field with validation error

346

location (str): Request location where error occurred

347

"""

348

```

349

350

## Integration with Testing Frameworks

351

352

### Pytest Integration

353

354

```python

355

# conftest.py

356

import pytest

357

from webargs.testing import CommonTestCase

358

359

class BaseParserTest(CommonTestCase):

360

"""Base class for all parser tests."""

361

362

@pytest.fixture(autouse=True)

363

def setup_test_data(self):

364

"""Setup test data before each test."""

365

self.test_user = {"name": "Test User", "age": 25}

366

```

367

368

### Unittest Integration

369

370

```python

371

import unittest

372

from webargs.testing import CommonTestCase

373

374

class TestParser(CommonTestCase, unittest.TestCase):

375

"""Integration with unittest framework."""

376

377

def setUp(self):

378

"""Setup method called before each test."""

379

super().setUp()

380

self.test_data = {"key": "value"}

381

382

def tearDown(self):

383

"""Cleanup method called after each test."""

384

super().tearDown()

385

```

386

387

## Types

388

389

```python { .api }

390

TestApp = webtest.TestApp

391

WSGIApplication = typing.Callable[[dict, typing.Callable], typing.Iterable[bytes]]

392

```