or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client.mddata.mdindex.mdserver.mdtesting.mdutilities.mdwebsocket.md

testing.mddocs/

0

# Testing

1

2

Comprehensive testing infrastructure for aiohttp applications including test servers, test clients, pytest integration, and utilities for both client and server testing. Provides complete testing capabilities with proper async support and resource management.

3

4

## Capabilities

5

6

### Test Server Infrastructure

7

8

Test server implementations for testing web applications and request handlers in controlled environments.

9

10

```python { .api }

11

class BaseTestServer:

12

def __init__(self, *, scheme='http', host='127.0.0.1', port=None, **kwargs):

13

"""

14

Base test server.

15

16

Parameters:

17

- scheme (str): URL scheme (http/https)

18

- host (str): Server host

19

- port (int): Server port (auto-assigned if None)

20

"""

21

22

async def start_server(self, **kwargs):

23

"""Start test server."""

24

25

async def close(self):

26

"""Close test server."""

27

28

@property

29

def host(self):

30

"""Server host."""

31

32

@property

33

def port(self):

34

"""Server port."""

35

36

@property

37

def scheme(self):

38

"""URL scheme."""

39

40

def make_url(self, path):

41

"""Create URL for path."""

42

43

class TestServer(BaseTestServer):

44

def __init__(self, app, *, port=None, **kwargs):

45

"""

46

Test server for web applications.

47

48

Parameters:

49

- app: Web application instance

50

- port (int): Server port

51

"""

52

53

class RawTestServer(BaseTestServer):

54

def __init__(self, handler, *, port=None, **kwargs):

55

"""

56

Raw test server for request handlers.

57

58

Parameters:

59

- handler: Request handler function

60

- port (int): Server port

61

"""

62

```

63

64

### Test Client

65

66

HTTP client specifically designed for testing with automatic server integration and simplified request methods.

67

68

```python { .api }

69

class TestClient:

70

def __init__(self, server, *, cookie_jar=None, **kwargs):

71

"""

72

Test HTTP client.

73

74

Parameters:

75

- server: Test server instance

76

- cookie_jar: Cookie jar for client

77

"""

78

79

async def start_server(self):

80

"""Start associated test server."""

81

82

async def close(self):

83

"""Close client and server."""

84

85

def make_url(self, path):

86

"""Create URL for path."""

87

88

async def request(self, method, path, **kwargs):

89

"""Make HTTP request to test server."""

90

91

async def get(self, path, **kwargs):

92

"""Make GET request."""

93

94

async def post(self, path, **kwargs):

95

"""Make POST request."""

96

97

async def put(self, path, **kwargs):

98

"""Make PUT request."""

99

100

async def patch(self, path, **kwargs):

101

"""Make PATCH request."""

102

103

async def delete(self, path, **kwargs):

104

"""Make DELETE request."""

105

106

async def head(self, path, **kwargs):

107

"""Make HEAD request."""

108

109

async def options(self, path, **kwargs):

110

"""Make OPTIONS request."""

111

112

async def ws_connect(self, path, **kwargs):

113

"""Establish WebSocket connection."""

114

115

@property

116

def session(self):

117

"""Underlying HTTP session."""

118

119

@property

120

def host(self):

121

"""Server host."""

122

123

@property

124

def port(self):

125

"""Server port."""

126

```

127

128

### Test Case Base Class

129

130

Base test case class providing utilities and setup for aiohttp application testing.

131

132

```python { .api }

133

class AioHTTPTestCase:

134

"""Base test case for aiohttp applications."""

135

136

async def get_application(self):

137

"""

138

Create application for testing.

139

Must be implemented by subclasses.

140

141

Returns:

142

Application: Web application instance

143

"""

144

145

async def setUpAsync(self):

146

"""Async setup method."""

147

148

async def tearDownAsync(self):

149

"""Async teardown method."""

150

151

async def get_server(self, app):

152

"""

153

Create test server for application.

154

155

Parameters:

156

- app: Web application

157

158

Returns:

159

TestServer: Test server instance

160

"""

161

162

async def get_client(self, server):

163

"""

164

Create test client for server.

165

166

Parameters:

167

- server: Test server

168

169

Returns:

170

TestClient: Test client instance

171

"""

172

173

@property

174

def client(self):

175

"""Test client instance."""

176

177

@property

178

def server(self):

179

"""Test server instance."""

180

181

@property

182

def app(self):

183

"""Web application instance."""

184

```

185

186

### Testing Utilities

187

188

Utility functions for creating mocked objects and managing test environments.

189

190

```python { .api }

191

def unused_port():

192

"""

193

Get unused TCP port.

194

195

Returns:

196

int: Available port number

197

"""

198

199

def make_mocked_request(

200

method,

201

path,

202

headers=None,

203

*,

204

version=None,

205

closing=False,

206

app=None,

207

writer=None,

208

protocol=None,

209

transport=None,

210

payload=None,

211

sslcontext=None,

212

client_max_size=1024**2,

213

loop=None,

214

match_info=None

215

):

216

"""

217

Create mocked request for testing.

218

219

Parameters:

220

- method (str): HTTP method

221

- path (str): Request path

222

- headers (dict): Request headers

223

- version: HTTP version

224

- closing (bool): Connection closing

225

- app: Application instance

226

- writer: Response writer

227

- protocol: Request protocol

228

- transport: Network transport

229

- payload: Request payload

230

- sslcontext: SSL context

231

- client_max_size (int): Max client payload size

232

- loop: Event loop

233

- match_info: URL match info

234

235

Returns:

236

Request: Mocked request object

237

"""

238

239

def make_mocked_coro(return_value=None, raise_exception=None):

240

"""

241

Create mocked coroutine.

242

243

Parameters:

244

- return_value: Value to return from coroutine

245

- raise_exception: Exception to raise from coroutine

246

247

Returns:

248

Mock: Mocked coroutine function

249

"""

250

251

def setup_test_loop(loop_factory=None):

252

"""

253

Setup event loop for testing.

254

255

Parameters:

256

- loop_factory: Factory function for creating loop

257

258

Returns:

259

Event loop instance

260

"""

261

262

def teardown_test_loop(loop, fast=False):

263

"""

264

Teardown test event loop.

265

266

Parameters:

267

- loop: Event loop to teardown

268

- fast (bool): Fast teardown mode

269

"""

270

271

def loop_context(loop_factory=None, fast=False):

272

"""

273

Context manager for test event loop.

274

275

Parameters:

276

- loop_factory: Factory function for creating loop

277

- fast (bool): Fast teardown mode

278

279

Returns:

280

Context manager yielding event loop

281

"""

282

283

def unittest_run_loop(func):

284

"""

285

Decorator to run test function in event loop.

286

287

Parameters:

288

- func: Test function to run

289

290

Returns:

291

Wrapped test function

292

"""

293

```

294

295

### Pytest Integration

296

297

Pytest fixtures and utilities for seamless integration with pytest testing framework.

298

299

```python { .api }

300

# Pytest fixtures (available when using aiohttp.pytest_plugin)

301

302

def loop():

303

"""

304

Event loop fixture.

305

306

Yields:

307

Event loop for test

308

"""

309

310

def unused_port():

311

"""

312

Unused port fixture.

313

314

Returns:

315

int: Available port number

316

"""

317

318

def aiohttp_client():

319

"""

320

Test client factory fixture.

321

322

Returns:

323

Function: Factory function for creating test clients

324

"""

325

326

def aiohttp_server():

327

"""

328

Test server factory fixture.

329

330

Returns:

331

Function: Factory function for creating test servers

332

"""

333

334

def aiohttp_raw_server():

335

"""

336

Raw test server factory fixture.

337

338

Returns:

339

Function: Factory function for creating raw test servers

340

"""

341

```

342

343

## Usage Examples

344

345

### Basic Application Testing

346

347

```python

348

import pytest

349

from aiohttp import web

350

import aiohttp

351

from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop

352

353

class TestWebApp(AioHTTPTestCase):

354

async def get_application(self):

355

"""Create test application."""

356

async def hello(request):

357

return web.Response(text='Hello, World!')

358

359

async def json_handler(request):

360

return web.json_response({'message': 'Hello, JSON!'})

361

362

app = web.Application()

363

app.router.add_get('/', hello)

364

app.router.add_get('/json', json_handler)

365

return app

366

367

@unittest_run_loop

368

async def test_hello(self):

369

"""Test hello endpoint."""

370

resp = await self.client.request('GET', '/')

371

self.assertEqual(resp.status, 200)

372

text = await resp.text()

373

self.assertEqual(text, 'Hello, World!')

374

375

@unittest_run_loop

376

async def test_json(self):

377

"""Test JSON endpoint."""

378

resp = await self.client.request('GET', '/json')

379

self.assertEqual(resp.status, 200)

380

data = await resp.json()

381

self.assertEqual(data['message'], 'Hello, JSON!')

382

```

383

384

### Pytest-based Testing

385

386

```python

387

import pytest

388

from aiohttp import web

389

390

async def hello_handler(request):

391

name = request.query.get('name', 'World')

392

return web.Response(text=f'Hello, {name}!')

393

394

async def create_app():

395

app = web.Application()

396

app.router.add_get('/hello', hello_handler)

397

return app

398

399

@pytest.fixture

400

async def client(aiohttp_client):

401

"""Create test client."""

402

app = await create_app()

403

return await aiohttp_client(app)

404

405

async def test_hello_default(client):

406

"""Test hello with default name."""

407

resp = await client.get('/hello')

408

assert resp.status == 200

409

text = await resp.text()

410

assert text == 'Hello, World!'

411

412

async def test_hello_custom_name(client):

413

"""Test hello with custom name."""

414

resp = await client.get('/hello?name=Alice')

415

assert resp.status == 200

416

text = await resp.text()

417

assert text == 'Hello, Alice!'

418

419

async def test_hello_post_method(client):

420

"""Test unsupported POST method."""

421

resp = await client.post('/hello')

422

assert resp.status == 405 # Method Not Allowed

423

```

424

425

### WebSocket Testing

426

427

```python

428

import pytest

429

from aiohttp import web, WSMsgType

430

import json

431

432

async def websocket_handler(request):

433

ws = web.WebSocketResponse()

434

await ws.prepare(request)

435

436

async for msg in ws:

437

if msg.type == WSMsgType.TEXT:

438

data = json.loads(msg.data)

439

if data['type'] == 'echo':

440

await ws.send_json({

441

'type': 'echo_response',

442

'message': data['message']

443

})

444

elif msg.type == WSMsgType.ERROR:

445

print(f'WebSocket error: {ws.exception()}')

446

447

return ws

448

449

@pytest.fixture

450

async def websocket_client(aiohttp_client):

451

"""Create WebSocket test client."""

452

app = web.Application()

453

app.router.add_get('/ws', websocket_handler)

454

client = await aiohttp_client(app)

455

return client

456

457

async def test_websocket_echo(websocket_client):

458

"""Test WebSocket echo functionality."""

459

async with websocket_client.ws_connect('/ws') as ws:

460

# Send echo request

461

await ws.send_json({

462

'type': 'echo',

463

'message': 'Hello, WebSocket!'

464

})

465

466

# Receive echo response

467

msg = await ws.receive()

468

assert msg.type == WSMsgType.TEXT

469

470

data = json.loads(msg.data)

471

assert data['type'] == 'echo_response'

472

assert data['message'] == 'Hello, WebSocket!'

473

```

474

475

### API Testing with Database

476

477

```python

478

import pytest

479

from aiohttp import web

480

import aiohttp

481

import asyncio

482

import sqlite3

483

from contextlib import asynccontextmanager

484

485

class UserService:

486

def __init__(self, db_path=':memory:'):

487

self.db_path = db_path

488

self.connection = None

489

490

async def initialize(self):

491

"""Initialize database."""

492

# In real applications, use aiopg, aiomysql, etc.

493

self.connection = sqlite3.connect(self.db_path)

494

self.connection.execute('''

495

CREATE TABLE IF NOT EXISTS users (

496

id INTEGER PRIMARY KEY,

497

name TEXT NOT NULL,

498

email TEXT UNIQUE NOT NULL

499

)

500

''')

501

self.connection.commit()

502

503

async def create_user(self, name, email):

504

"""Create new user."""

505

cursor = self.connection.execute(

506

'INSERT INTO users (name, email) VALUES (?, ?)',

507

(name, email)

508

)

509

self.connection.commit()

510

return cursor.lastrowid

511

512

async def get_user(self, user_id):

513

"""Get user by ID."""

514

cursor = self.connection.execute(

515

'SELECT id, name, email FROM users WHERE id = ?',

516

(user_id,)

517

)

518

row = cursor.fetchone()

519

if row:

520

return {'id': row[0], 'name': row[1], 'email': row[2]}

521

return None

522

523

async def close(self):

524

"""Close database connection."""

525

if self.connection:

526

self.connection.close()

527

528

async def create_user_handler(request):

529

"""Create new user."""

530

data = await request.json()

531

user_service = request.app['user_service']

532

533

try:

534

user_id = await user_service.create_user(

535

data['name'],

536

data['email']

537

)

538

return web.json_response({

539

'id': user_id,

540

'name': data['name'],

541

'email': data['email']

542

}, status=201)

543

544

except sqlite3.IntegrityError:

545

raise web.HTTPConflict(text='Email already exists')

546

547

async def get_user_handler(request):

548

"""Get user by ID."""

549

user_id = int(request.match_info['user_id'])

550

user_service = request.app['user_service']

551

552

user = await user_service.get_user(user_id)

553

if user:

554

return web.json_response(user)

555

else:

556

raise web.HTTPNotFound()

557

558

async def create_app():

559

"""Create application with user service."""

560

app = web.Application()

561

562

# Initialize user service

563

user_service = UserService()

564

await user_service.initialize()

565

app['user_service'] = user_service

566

567

# Add routes

568

app.router.add_post('/users', create_user_handler)

569

app.router.add_get('/users/{user_id}', get_user_handler)

570

571

# Cleanup

572

async def cleanup_user_service(app):

573

await app['user_service'].close()

574

575

app.on_cleanup.append(cleanup_user_service)

576

577

return app

578

579

@pytest.fixture

580

async def client(aiohttp_client):

581

"""Create test client with database."""

582

app = await create_app()

583

return await aiohttp_client(app)

584

585

async def test_create_user(client):

586

"""Test user creation."""

587

user_data = {

588

'name': 'John Doe',

589

'email': 'john@example.com'

590

}

591

592

resp = await client.post('/users', json=user_data)

593

assert resp.status == 201

594

595

data = await resp.json()

596

assert data['name'] == 'John Doe'

597

assert data['email'] == 'john@example.com'

598

assert 'id' in data

599

600

async def test_get_user(client):

601

"""Test user retrieval."""

602

# First create a user

603

user_data = {

604

'name': 'Jane Doe',

605

'email': 'jane@example.com'

606

}

607

608

create_resp = await client.post('/users', json=user_data)

609

created_user = await create_resp.json()

610

user_id = created_user['id']

611

612

# Then retrieve the user

613

get_resp = await client.get(f'/users/{user_id}')

614

assert get_resp.status == 200

615

616

user = await get_resp.json()

617

assert user['id'] == user_id

618

assert user['name'] == 'Jane Doe'

619

assert user['email'] == 'jane@example.com'

620

621

async def test_get_nonexistent_user(client):

622

"""Test getting non-existent user."""

623

resp = await client.get('/users/999')

624

assert resp.status == 404

625

626

async def test_duplicate_email(client):

627

"""Test creating user with duplicate email."""

628

user_data = {

629

'name': 'User One',

630

'email': 'duplicate@example.com'

631

}

632

633

# Create first user

634

resp1 = await client.post('/users', json=user_data)

635

assert resp1.status == 201

636

637

# Try to create user with same email

638

user_data['name'] = 'User Two'

639

resp2 = await client.post('/users', json=user_data)

640

assert resp2.status == 409 # Conflict

641

```

642

643

### Load Testing Utilities

644

645

```python

646

import asyncio

647

import aiohttp

648

import time

649

from concurrent.futures import ThreadPoolExecutor

650

651

async def load_test_endpoint(url, num_requests=100, concurrency=10):

652

"""

653

Perform load testing on an endpoint.

654

655

Parameters:

656

- url (str): Endpoint URL to test

657

- num_requests (int): Total number of requests

658

- concurrency (int): Concurrent request limit

659

"""

660

661

semaphore = asyncio.Semaphore(concurrency)

662

results = []

663

664

async def make_request(session, request_id):

665

async with semaphore:

666

start_time = time.time()

667

try:

668

async with session.get(url) as response:

669

status = response.status

670

# Consume response to complete request

671

await response.read()

672

673

end_time = time.time()

674

return {

675

'request_id': request_id,

676

'status': status,

677

'duration': end_time - start_time,

678

'success': 200 <= status < 300

679

}

680

681

except Exception as e:

682

end_time = time.time()

683

return {

684

'request_id': request_id,

685

'status': 0,

686

'duration': end_time - start_time,

687

'success': False,

688

'error': str(e)

689

}

690

691

# Perform load test

692

start_time = time.time()

693

694

async with aiohttp.ClientSession() as session:

695

tasks = [

696

make_request(session, i)

697

for i in range(num_requests)

698

]

699

results = await asyncio.gather(*tasks)

700

701

end_time = time.time()

702

total_duration = end_time - start_time

703

704

# Calculate statistics

705

successful_requests = [r for r in results if r['success']]

706

failed_requests = [r for r in results if not r['success']]

707

708

if successful_requests:

709

durations = [r['duration'] for r in successful_requests]

710

avg_duration = sum(durations) / len(durations)

711

min_duration = min(durations)

712

max_duration = max(durations)

713

else:

714

avg_duration = min_duration = max_duration = 0

715

716

return {

717

'total_requests': num_requests,

718

'successful_requests': len(successful_requests),

719

'failed_requests': len(failed_requests),

720

'success_rate': len(successful_requests) / num_requests * 100,

721

'total_duration': total_duration,

722

'requests_per_second': num_requests / total_duration,

723

'avg_response_time': avg_duration,

724

'min_response_time': min_duration,

725

'max_response_time': max_duration

726

}

727

728

# Usage example

729

async def test_api_performance():

730

"""Test API performance under load."""

731

results = await load_test_endpoint(

732

'http://localhost:8080/api/test',

733

num_requests=1000,

734

concurrency=50

735

)

736

737

print(f"Load Test Results:")

738

print(f"Total Requests: {results['total_requests']}")

739

print(f"Successful: {results['successful_requests']}")

740

print(f"Failed: {results['failed_requests']}")

741

print(f"Success Rate: {results['success_rate']:.2f}%")

742

print(f"Requests/sec: {results['requests_per_second']:.2f}")

743

print(f"Avg Response Time: {results['avg_response_time']:.3f}s")

744

print(f"Min Response Time: {results['min_response_time']:.3f}s")

745

print(f"Max Response Time: {results['max_response_time']:.3f}s")

746

747

# Run load test

748

asyncio.run(test_api_performance())

749

```