or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application.mdasgi-websocket.mderror-handling.mdindex.mdmedia.mdmiddleware-hooks.mdrequest-response.mdrouting.mdtesting.mdutilities.md

testing.mddocs/

0

# Testing Framework

1

2

Comprehensive testing utilities for both WSGI and ASGI Falcon applications. Provides request simulation, response validation, WebSocket testing, and mock objects for building robust test suites.

3

4

## Capabilities

5

6

### WSGI Test Client

7

8

Full-featured test client for simulating HTTP requests against WSGI applications.

9

10

```python { .api }

11

class TestClient:

12

def __init__(self, app: object, headers: dict = None):

13

"""

14

Create WSGI test client.

15

16

Args:

17

app: WSGI Falcon application instance

18

headers: Default headers for all requests

19

"""

20

21

def simulate_request(

22

self,

23

method: str,

24

path: str,

25

query_string: str = None,

26

headers: dict = None,

27

body: str = None,

28

json: object = None,

29

**kwargs

30

) -> Result:

31

"""

32

Simulate generic HTTP request.

33

34

Args:

35

method: HTTP method (GET, POST, PUT, etc.)

36

path: Request path (e.g., '/users/123')

37

query_string: URL query parameters

38

headers: Request headers

39

body: Raw request body

40

json: JSON request body (auto-serialized)

41

**kwargs: Additional WSGI environment variables

42

43

Returns:

44

Result object with response data

45

"""

46

47

def simulate_get(self, path: str, **kwargs) -> Result:

48

"""Simulate HTTP GET request"""

49

50

def simulate_post(self, path: str, **kwargs) -> Result:

51

"""Simulate HTTP POST request"""

52

53

def simulate_put(self, path: str, **kwargs) -> Result:

54

"""Simulate HTTP PUT request"""

55

56

def simulate_patch(self, path: str, **kwargs) -> Result:

57

"""Simulate HTTP PATCH request"""

58

59

def simulate_delete(self, path: str, **kwargs) -> Result:

60

"""Simulate HTTP DELETE request"""

61

62

def simulate_head(self, path: str, **kwargs) -> Result:

63

"""Simulate HTTP HEAD request"""

64

65

def simulate_options(self, path: str, **kwargs) -> Result:

66

"""Simulate HTTP OPTIONS request"""

67

```

68

69

#### Basic Testing Example

70

71

```python

72

import falcon

73

from falcon.testing import TestClient

74

75

class UserResource:

76

def on_get(self, req, resp, user_id=None):

77

if user_id:

78

resp.media = {'id': user_id, 'name': 'Test User'}

79

else:

80

resp.media = {'users': []}

81

82

def on_post(self, req, resp):

83

user_data = req.media

84

resp.status = falcon.HTTP_201

85

resp.media = {'created': user_data}

86

87

# Create app and test client

88

app = falcon.App()

89

app.add_route('/users', UserResource())

90

app.add_route('/users/{user_id}', UserResource())

91

92

client = TestClient(app)

93

94

# Test GET request

95

result = client.simulate_get('/users/123')

96

assert result.status_code == 200

97

assert result.json['name'] == 'Test User'

98

99

# Test POST request

100

result = client.simulate_post('/users', json={'name': 'New User'})

101

assert result.status_code == 201

102

assert result.json['created']['name'] == 'New User'

103

```

104

105

### ASGI Test Client

106

107

ASGI testing conductor for async applications and WebSocket testing.

108

109

```python { .api }

110

class ASGIConductor:

111

def __init__(self, app: object):

112

"""

113

Create ASGI test conductor.

114

115

Args:

116

app: ASGI Falcon application instance

117

"""

118

119

async def simulate_request(

120

self,

121

method: str,

122

path: str,

123

query_string: str = None,

124

headers: dict = None,

125

body: bytes = None,

126

json: object = None,

127

**kwargs

128

) -> Result:

129

"""

130

Simulate async HTTP request.

131

132

Args:

133

method: HTTP method

134

path: Request path

135

query_string: URL query parameters

136

headers: Request headers

137

body: Raw request body bytes

138

json: JSON request body (auto-serialized)

139

**kwargs: Additional ASGI scope variables

140

141

Returns:

142

Result object with response data

143

"""

144

145

async def simulate_get(self, path: str, **kwargs) -> Result:

146

"""Simulate async HTTP GET request"""

147

148

async def simulate_post(self, path: str, **kwargs) -> Result:

149

"""Simulate async HTTP POST request"""

150

151

async def simulate_put(self, path: str, **kwargs) -> Result:

152

"""Simulate async HTTP PUT request"""

153

154

async def simulate_patch(self, path: str, **kwargs) -> Result:

155

"""Simulate async HTTP PATCH request"""

156

157

async def simulate_delete(self, path: str, **kwargs) -> Result:

158

"""Simulate async HTTP DELETE request"""

159

160

async def simulate_head(self, path: str, **kwargs) -> Result:

161

"""Simulate async HTTP HEAD request"""

162

163

async def simulate_options(self, path: str, **kwargs) -> Result:

164

"""Simulate async HTTP OPTIONS request"""

165

```

166

167

### WebSocket Testing

168

169

Utilities for testing WebSocket connections in ASGI applications.

170

171

```python { .api }

172

class ASGIWebSocketSimulator:

173

def __init__(self, app: object, path: str, headers: dict = None):

174

"""

175

Create WebSocket test simulator.

176

177

Args:

178

app: ASGI Falcon application

179

path: WebSocket path

180

headers: Connection headers

181

"""

182

183

async def __aenter__(self):

184

"""Async context manager entry"""

185

return self

186

187

async def __aexit__(self, exc_type, exc_val, exc_tb):

188

"""Async context manager exit"""

189

190

async def send_text(self, text: str):

191

"""

192

Send text message to WebSocket.

193

194

Args:

195

text: Text message to send

196

"""

197

198

async def send_data(self, data: bytes):

199

"""

200

Send binary data to WebSocket.

201

202

Args:

203

data: Binary data to send

204

"""

205

206

async def receive_text(self) -> str:

207

"""

208

Receive text message from WebSocket.

209

210

Returns:

211

Text message received

212

"""

213

214

async def receive_data(self) -> bytes:

215

"""

216

Receive binary data from WebSocket.

217

218

Returns:

219

Binary data received

220

"""

221

222

async def close(self, code: int = 1000):

223

"""

224

Close WebSocket connection.

225

226

Args:

227

code: WebSocket close code

228

"""

229

```

230

231

#### ASGI and WebSocket Testing Example

232

233

```python

234

import falcon.asgi

235

from falcon.testing import ASGIConductor, ASGIWebSocketSimulator

236

237

class AsyncUserResource:

238

async def on_get(self, req, resp, user_id=None):

239

# Simulate async database call

240

user = await fetch_user(user_id)

241

resp.media = user

242

243

class WebSocketEcho:

244

async def on_websocket(self, req, ws):

245

await ws.accept()

246

while True:

247

try:

248

message = await ws.receive_text()

249

await ws.send_text(f"Echo: {message}")

250

except falcon.WebSocketDisconnected:

251

break

252

253

# Create ASGI app

254

app = falcon.asgi.App()

255

app.add_route('/users/{user_id}', AsyncUserResource())

256

app.add_route('/echo', WebSocketEcho())

257

258

# Test HTTP endpoint

259

conductor = ASGIConductor(app)

260

result = await conductor.simulate_get('/users/123')

261

assert result.status_code == 200

262

263

# Test WebSocket

264

async with ASGIWebSocketSimulator(app, '/echo') as ws:

265

await ws.send_text('Hello')

266

response = await ws.receive_text()

267

assert response == 'Echo: Hello'

268

```

269

270

### Test Result Objects

271

272

Objects representing HTTP response data for assertions and validation.

273

274

```python { .api }

275

class Result:

276

def __init__(self, status: str, headers: list, body: bytes):

277

"""

278

HTTP response result.

279

280

Args:

281

status: HTTP status line

282

headers: Response headers list

283

body: Response body bytes

284

"""

285

286

# Properties

287

status: str # Full status line (e.g., '200 OK')

288

status_code: int # Status code only (e.g., 200)

289

headers: dict # Headers as case-insensitive dict

290

text: str # Response body as text

291

content: bytes # Raw response body

292

json: object # Parsed JSON response (if applicable)

293

cookies: list # Response cookies

294

295

# Methods

296

def __len__(self) -> int:

297

"""Get response body length"""

298

299

def __iter__(self):

300

"""Iterate over response body bytes"""

301

302

class ResultBodyStream:

303

def __init__(self, result: Result):

304

"""

305

Streaming wrapper for large response bodies.

306

307

Args:

308

result: Result object to stream

309

"""

310

311

def read(self, size: int = -1) -> bytes:

312

"""Read bytes from response body"""

313

314

class StreamedResult:

315

def __init__(self, headers: dict, stream: object):

316

"""

317

Result for streamed responses.

318

319

Args:

320

headers: Response headers

321

stream: Response body stream

322

"""

323

324

# Properties

325

headers: dict

326

stream: object

327

328

class Cookie:

329

def __init__(self, name: str, value: str, **attributes):

330

"""

331

Response cookie representation.

332

333

Args:

334

name: Cookie name

335

value: Cookie value

336

**attributes: Cookie attributes (path, domain, secure, etc.)

337

"""

338

339

# Properties

340

name: str

341

value: str

342

path: str

343

domain: str

344

secure: bool

345

http_only: bool

346

max_age: int

347

expires: str

348

```

349

350

### Test Helper Functions

351

352

Utility functions for creating test environments and mock objects.

353

354

```python { .api }

355

# WSGI test helpers

356

def create_environ(

357

method: str = 'GET',

358

path: str = '/',

359

query_string: str = '',

360

headers: dict = None,

361

body: str = '',

362

**kwargs

363

) -> dict:

364

"""

365

Create WSGI environment dictionary for testing.

366

367

Args:

368

method: HTTP method

369

path: Request path

370

query_string: Query parameters

371

headers: Request headers

372

body: Request body

373

**kwargs: Additional environment variables

374

375

Returns:

376

WSGI environment dict

377

"""

378

379

def create_req(app: object, **kwargs) -> object:

380

"""

381

Create Request object for testing.

382

383

Args:

384

app: Falcon application

385

**kwargs: Environment parameters

386

387

Returns:

388

Request object

389

"""

390

391

# ASGI test helpers

392

def create_scope(

393

type: str = 'http',

394

method: str = 'GET',

395

path: str = '/',

396

query_string: str = '',

397

headers: list = None,

398

**kwargs

399

) -> dict:

400

"""

401

Create ASGI scope dictionary for testing.

402

403

Args:

404

type: ASGI scope type ('http' or 'websocket')

405

method: HTTP method

406

path: Request path

407

query_string: Query parameters

408

headers: Request headers as list of tuples

409

**kwargs: Additional scope variables

410

411

Returns:

412

ASGI scope dict

413

"""

414

415

def create_asgi_req(app: object, **kwargs) -> object:

416

"""

417

Create ASGI Request object for testing.

418

419

Args:

420

app: ASGI Falcon application

421

**kwargs: Scope parameters

422

423

Returns:

424

ASGI Request object

425

"""

426

427

# Event simulation

428

class ASGIRequestEventEmitter:

429

def __init__(self, body: bytes = b''):

430

"""

431

ASGI request event emitter for testing.

432

433

Args:

434

body: Request body bytes

435

"""

436

437

async def emit(self) -> dict:

438

"""Emit ASGI request events"""

439

440

class ASGIResponseEventCollector:

441

def __init__(self):

442

"""ASGI response event collector for testing."""

443

444

async def collect(self, message: dict):

445

"""Collect ASGI response events"""

446

447

def get_response(self) -> tuple:

448

"""Get collected response data"""

449

450

# Utility functions

451

def get_unused_port(family: int = socket.AF_INET, type: int = socket.SOCK_STREAM) -> int:

452

"""

453

Get unused network port for testing.

454

455

Args:

456

family: Socket family

457

type: Socket type

458

459

Returns:

460

Unused port number

461

"""

462

463

def rand_string(length: int, alphabet: str = None) -> str:

464

"""

465

Generate random string for testing.

466

467

Args:

468

length: String length

469

alphabet: Character set to use

470

471

Returns:

472

Random string

473

"""

474

```

475

476

### Test Resources and Mocks

477

478

Pre-built test resources and mock objects for common testing scenarios.

479

480

```python { .api }

481

class SimpleTestResource:

482

def __init__(self, status: str = '200 OK', body: str = 'Test'):

483

"""

484

Simple test resource for basic testing.

485

486

Args:

487

status: HTTP status to return

488

body: Response body

489

"""

490

491

def on_get(self, req, resp):

492

"""Handle GET requests"""

493

494

def on_post(self, req, resp):

495

"""Handle POST requests"""

496

497

class SimpleTestResourceAsync:

498

def __init__(self, status: str = '200 OK', body: str = 'Test'):

499

"""

500

Simple async test resource.

501

502

Args:

503

status: HTTP status to return

504

body: Response body

505

"""

506

507

async def on_get(self, req, resp):

508

"""Handle async GET requests"""

509

510

def capture_responder_args(*args, **kwargs) -> dict:

511

"""

512

Capture responder method arguments for testing.

513

514

Args:

515

*args: Positional arguments

516

**kwargs: Keyword arguments

517

518

Returns:

519

Captured arguments dict

520

"""

521

522

def set_resp_defaults(resp: object, base_resp: object):

523

"""

524

Set default response values for testing.

525

526

Args:

527

resp: Response object to configure

528

base_resp: Base response with defaults

529

"""

530

531

class StartResponseMock:

532

def __init__(self):

533

"""Mock WSGI start_response callable for testing."""

534

535

def __call__(self, status: str, headers: list, exc_info: tuple = None):

536

"""WSGI start_response interface"""

537

538

# Properties

539

status: str

540

headers: list

541

exc_info: tuple

542

```

543

544

### Test Case Base Class

545

546

Base test case class with Falcon-specific helper methods.

547

548

```python { .api }

549

class TestCase(unittest.TestCase):

550

def setUp(self):

551

"""Test setup with Falcon app creation."""

552

self.app = falcon.App()

553

self.client = TestClient(self.app)

554

555

def simulate_request(self, *args, **kwargs):

556

"""Simulate request using test client"""

557

return self.client.simulate_request(*args, **kwargs)

558

559

def simulate_get(self, *args, **kwargs):

560

"""Simulate GET request"""

561

return self.client.simulate_get(*args, **kwargs)

562

563

def simulate_post(self, *args, **kwargs):

564

"""Simulate POST request"""

565

return self.client.simulate_post(*args, **kwargs)

566

567

# Additional simulate_* methods for other HTTP verbs

568

```

569

570

#### Test Case Usage Example

571

572

```python

573

import unittest

574

import falcon

575

from falcon.testing import TestCase

576

577

class UserResourceTest(TestCase):

578

def setUp(self):

579

super().setUp()

580

self.app.add_route('/users/{user_id}', UserResource())

581

582

def test_get_user(self):

583

"""Test getting user by ID"""

584

result = self.simulate_get('/users/123')

585

self.assertEqual(result.status_code, 200)

586

self.assertEqual(result.json['id'], '123')

587

588

def test_user_not_found(self):

589

"""Test user not found scenario"""

590

result = self.simulate_get('/users/999')

591

self.assertEqual(result.status_code, 404)

592

593

def test_create_user(self):

594

"""Test user creation"""

595

user_data = {'name': 'Test User', 'email': 'test@example.com'}

596

result = self.simulate_post('/users', json=user_data)

597

self.assertEqual(result.status_code, 201)

598

self.assertEqual(result.json['name'], 'Test User')

599

```

600

601

## Types

602

603

```python { .api }

604

# Test clients

605

TestClient: type # WSGI test client

606

ASGIConductor: type # ASGI test conductor

607

608

# WebSocket testing

609

ASGIWebSocketSimulator: type

610

611

# Result objects

612

Result: type

613

ResultBodyStream: type

614

StreamedResult: type

615

Cookie: type

616

617

# Test helpers

618

create_environ: callable

619

create_req: callable

620

create_scope: callable

621

create_asgi_req: callable

622

623

# Event simulation

624

ASGIRequestEventEmitter: type

625

ASGIResponseEventCollector: type

626

627

# Utilities

628

get_unused_port: callable

629

rand_string: callable

630

631

# Test resources

632

SimpleTestResource: type

633

SimpleTestResourceAsync: type

634

capture_responder_args: callable

635

set_resp_defaults: callable

636

StartResponseMock: type

637

638

# Test case base

639

TestCase: type

640

```