or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

additional.mdauth.mdclient.mdcore-server.mdindex.mdrequest-response.mdtesting.mdwebsockets.md

testing.mddocs/

0

# Testing Utilities

1

2

BlackSheep provides comprehensive testing utilities for unit testing, integration testing, and end-to-end testing of web applications. The testing framework supports async testing, request simulation, and complete application testing without network overhead.

3

4

## TestClient

5

6

The `TestClient` class is the primary testing utility that allows you to make HTTP requests to your application without starting a server.

7

8

### Basic Testing Setup

9

10

```python { .api }

11

import pytest

12

import asyncio

13

from blacksheep.testing import TestClient

14

from blacksheep import Application, json, Request, FromJSON

15

16

# Sample application for testing

17

app = Application()

18

19

@app.get("/")

20

async def home():

21

return json({"message": "Hello, World!"})

22

23

@app.get("/users/{user_id:int}")

24

async def get_user(user_id: int):

25

return json({"id": user_id, "name": f"User {user_id}"})

26

27

@app.post("/users")

28

async def create_user(data: FromJSON[dict]):

29

return json({"created": True, "data": data.value})

30

31

# Basic test example

32

async def test_basic_endpoints():

33

client = TestClient(app)

34

35

# Test GET endpoint

36

response = await client.get("/")

37

assert response.status == 200

38

data = await response.json()

39

assert data["message"] == "Hello, World!"

40

41

# Test GET with parameters

42

response = await client.get("/users/123")

43

assert response.status == 200

44

data = await response.json()

45

assert data["id"] == 123

46

47

# Test POST with JSON

48

response = await client.post("/users", json={"name": "Alice"})

49

assert response.status == 200

50

data = await response.json()

51

assert data["created"] is True

52

53

# Run test

54

asyncio.run(test_basic_endpoints())

55

```

56

57

### PyTest Integration

58

59

```python { .api }

60

import pytest

61

from blacksheep.testing import TestClient

62

63

@pytest.fixture

64

def client():

65

"""Create test client fixture"""

66

return TestClient(app)

67

68

@pytest.mark.asyncio

69

async def test_home_endpoint(client):

70

response = await client.get("/")

71

assert response.status == 200

72

73

data = await response.json()

74

assert "message" in data

75

76

@pytest.mark.asyncio

77

async def test_user_creation(client):

78

user_data = {"name": "Alice", "email": "alice@example.com"}

79

response = await client.post("/users", json=user_data)

80

81

assert response.status == 200

82

result = await response.json()

83

assert result["created"] is True

84

assert result["data"]["name"] == "Alice"

85

86

@pytest.mark.asyncio

87

async def test_not_found(client):

88

response = await client.get("/nonexistent")

89

assert response.status == 404

90

```

91

92

### Request Methods

93

94

```python { .api }

95

from blacksheep.testing import TestClient

96

from blacksheep import JSONContent, TextContent, FormContent

97

98

async def test_http_methods():

99

client = TestClient(app)

100

101

# GET request

102

response = await client.get("/users")

103

assert response.status == 200

104

105

# GET with query parameters

106

response = await client.get("/users", params={"page": 1, "limit": 10})

107

# Equivalent to: /users?page=1&limit=10

108

109

# POST request with JSON

110

response = await client.post("/users", json={"name": "Bob"})

111

112

# POST with custom content

113

json_content = JSONContent({"user": {"name": "Charlie"}})

114

response = await client.post("/users", content=json_content)

115

116

# POST with form data

117

response = await client.post("/login", data={"username": "user", "password": "pass"})

118

119

# PUT request

120

response = await client.put("/users/123", json={"name": "Updated Name"})

121

122

# DELETE request

123

response = await client.delete("/users/123")

124

125

# PATCH request

126

response = await client.patch("/users/123", json={"email": "new@example.com"})

127

128

# HEAD request

129

response = await client.head("/users/123")

130

assert response.status == 200

131

# No response body for HEAD requests

132

133

# OPTIONS request

134

response = await client.options("/users")

135

```

136

137

### Headers and Cookies

138

139

```python { .api }

140

async def test_headers_and_cookies():

141

client = TestClient(app)

142

143

# Custom headers

144

headers = {"Authorization": "Bearer token123", "X-API-Version": "1.0"}

145

response = await client.get("/protected", headers=headers)

146

147

# Cookies

148

cookies = {"session_id": "abc123", "theme": "dark"}

149

response = await client.get("/dashboard", cookies=cookies)

150

151

# Both headers and cookies

152

response = await client.get(

153

"/api/data",

154

headers={"Authorization": "Bearer token"},

155

cookies={"session": "value"}

156

)

157

158

# Check response headers

159

assert response.headers.get_first(b"Content-Type") == b"application/json"

160

161

# Check response cookies

162

set_cookie_header = response.headers.get_first(b"Set-Cookie")

163

if set_cookie_header:

164

print(f"Server set cookie: {set_cookie_header.decode()}")

165

```

166

167

## File Upload Testing

168

169

Test file upload endpoints with multipart form data.

170

171

### File Upload Tests

172

173

```python { .api }

174

from blacksheep import MultiPartFormData, FormPart

175

from io import BytesIO

176

177

# Application with file upload

178

@app.post("/upload")

179

async def upload_file(files: FromFiles):

180

uploaded_files = files.value

181

return json({

182

"files_received": len(uploaded_files),

183

"filenames": [f.file_name.decode() if f.file_name else "unknown"

184

for f in uploaded_files]

185

})

186

187

async def test_file_upload():

188

client = TestClient(app)

189

190

# Create test file content

191

file_content = b"Test file content"

192

193

# Create form parts

194

text_part = FormPart(b"title", b"My Document")

195

file_part = FormPart(

196

name=b"document",

197

data=file_content,

198

content_type=b"text/plain",

199

file_name=b"test.txt"

200

)

201

202

# Create multipart form data

203

multipart = MultiPartFormData([text_part, file_part])

204

205

# Upload file

206

response = await client.post("/upload", content=multipart)

207

assert response.status == 200

208

209

result = await response.json()

210

assert result["files_received"] == 1

211

assert "test.txt" in result["filenames"]

212

213

# Alternative: Upload with files parameter

214

async def test_file_upload_simple():

215

client = TestClient(app)

216

217

# Simple file upload

218

file_data = BytesIO(b"Simple file content")

219

response = await client.post(

220

"/upload",

221

files={"document": ("simple.txt", file_data, "text/plain")}

222

)

223

224

assert response.status == 200

225

```

226

227

## Authentication Testing

228

229

Test authentication and authorization features.

230

231

### Authentication Tests

232

233

```python { .api }

234

from blacksheep import auth, allow_anonymous

235

from blacksheep.server.authentication.jwt import JWTBearerAuthentication

236

from guardpost import Identity

237

238

# Application with auth

239

auth_app = Application()

240

auth_strategy = auth_app.use_authentication()

241

242

@auth_app.get("/public")

243

@allow_anonymous

244

async def public_endpoint():

245

return json({"public": True})

246

247

@auth_app.get("/protected")

248

@auth()

249

async def protected_endpoint(request: Request):

250

identity = request.identity

251

return json({"user_id": identity.id, "authenticated": True})

252

253

async def test_authentication():

254

client = TestClient(auth_app)

255

256

# Test public endpoint

257

response = await client.get("/public")

258

assert response.status == 200

259

260

# Test protected endpoint without auth (should fail)

261

response = await client.get("/protected")

262

assert response.status == 401

263

264

# Test protected endpoint with auth

265

headers = {"Authorization": "Bearer valid-jwt-token"}

266

response = await client.get("/protected", headers=headers)

267

# Note: This will depend on your JWT validation logic

268

```

269

270

### Mock Authentication

271

272

```python { .api }

273

from unittest.mock import AsyncMock, patch

274

275

async def test_mock_authentication():

276

client = TestClient(auth_app)

277

278

# Mock the authentication handler

279

mock_identity = Identity({"sub": "user123", "name": "Test User"})

280

281

with patch.object(auth_strategy, 'authenticate', return_value=mock_identity):

282

response = await client.get("/protected")

283

assert response.status == 200

284

285

data = await response.json()

286

assert data["user_id"] == "user123"

287

assert data["authenticated"] is True

288

```

289

290

## Database Testing

291

292

Test applications that use databases with proper setup and teardown.

293

294

### Database Test Setup

295

296

```python { .api }

297

import pytest

298

from blacksheep import Application, FromServices

299

from rodi import Container

300

301

# Sample service and repository

302

class UserRepository:

303

def __init__(self):

304

self.users = {}

305

self.next_id = 1

306

307

async def create_user(self, user_data: dict) -> dict:

308

user = {"id": self.next_id, **user_data}

309

self.users[self.next_id] = user

310

self.next_id += 1

311

return user

312

313

async def get_user(self, user_id: int) -> dict:

314

return self.users.get(user_id)

315

316

# Application with database

317

db_app = Application()

318

319

# Configure DI

320

container = Container()

321

container.add_singleton(UserRepository)

322

db_app.services = container

323

324

@db_app.post("/users")

325

async def create_user_endpoint(

326

data: FromJSON[dict],

327

repo: FromServices[UserRepository]

328

):

329

user = await repo.create_user(data.value)

330

return json(user)

331

332

@db_app.get("/users/{user_id:int}")

333

async def get_user_endpoint(

334

user_id: int,

335

repo: FromServices[UserRepository]

336

):

337

user = await repo.get_user(user_id)

338

if not user:

339

return Response(404)

340

return json(user)

341

342

@pytest.fixture

343

async def db_client():

344

"""Create test client with fresh database"""

345

# Create fresh application instance for each test

346

test_app = Application()

347

test_container = Container()

348

test_container.add_singleton(UserRepository)

349

test_app.services = test_container

350

351

# Re-register routes (in real app, you'd organize this better)

352

test_app.post("/users")(create_user_endpoint)

353

test_app.get("/users/{user_id:int}")(get_user_endpoint)

354

355

return TestClient(test_app)

356

357

@pytest.mark.asyncio

358

async def test_user_crud(db_client):

359

# Create user

360

user_data = {"name": "Alice", "email": "alice@example.com"}

361

response = await db_client.post("/users", json=user_data)

362

assert response.status == 200

363

364

created_user = await response.json()

365

user_id = created_user["id"]

366

367

# Get user

368

response = await db_client.get(f"/users/{user_id}")

369

assert response.status == 200

370

371

retrieved_user = await response.json()

372

assert retrieved_user["name"] == "Alice"

373

assert retrieved_user["email"] == "alice@example.com"

374

375

# Get non-existent user

376

response = await db_client.get("/users/999")

377

assert response.status == 404

378

```

379

380

## WebSocket Testing

381

382

Test WebSocket endpoints and real-time functionality.

383

384

### WebSocket Test Setup

385

386

```python { .api }

387

from blacksheep.testing import TestClient

388

from blacksheep import WebSocket, WebSocketState

389

390

# WebSocket application

391

ws_app = Application()

392

393

@ws_app.ws("/echo")

394

async def echo_handler(websocket: WebSocket):

395

await websocket.accept()

396

397

try:

398

while True:

399

message = await websocket.receive_text()

400

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

401

except WebSocketDisconnectError:

402

pass

403

404

@ws_app.ws("/json-echo")

405

async def json_echo_handler(websocket: WebSocket):

406

await websocket.accept()

407

408

try:

409

while True:

410

data = await websocket.receive_json()

411

await websocket.send_json({"echo": data})

412

except WebSocketDisconnectError:

413

pass

414

415

async def test_websocket_echo():

416

client = TestClient(ws_app)

417

418

# Test WebSocket connection

419

async with client.websocket("/echo") as websocket:

420

# Send message

421

await websocket.send_text("Hello WebSocket!")

422

423

# Receive echo

424

response = await websocket.receive_text()

425

assert response == "Echo: Hello WebSocket!"

426

427

# Send another message

428

await websocket.send_text("Second message")

429

response = await websocket.receive_text()

430

assert response == "Echo: Second message"

431

432

async def test_websocket_json():

433

client = TestClient(ws_app)

434

435

async with client.websocket("/json-echo") as websocket:

436

# Send JSON data

437

test_data = {"message": "Hello", "count": 42}

438

await websocket.send_json(test_data)

439

440

# Receive JSON response

441

response = await websocket.receive_json()

442

assert response["echo"]["message"] == "Hello"

443

assert response["echo"]["count"] == 42

444

```

445

446

## Mock Objects

447

448

BlackSheep provides mock objects for testing ASGI applications and components.

449

450

### ASGI Mocks

451

452

```python { .api }

453

from blacksheep.testing import MockReceive, MockSend

454

455

async def test_asgi_mocks():

456

# Mock ASGI components

457

mock_receive = MockReceive()

458

mock_send = MockSend()

459

460

# Add messages to mock receive

461

mock_receive.add_message({

462

"type": "http.request",

463

"body": b'{"test": "data"}',

464

"more_body": False

465

})

466

467

# Test ASGI application directly

468

scope = {

469

"type": "http",

470

"method": "POST",

471

"path": "/test",

472

"headers": [(b"content-type", b"application/json")]

473

}

474

475

# Call application ASGI interface

476

await app(scope, mock_receive, mock_send)

477

478

# Check sent messages

479

sent_messages = mock_send.messages

480

assert len(sent_messages) > 0

481

482

# Verify response

483

response_start = sent_messages[0]

484

assert response_start["type"] == "http.response.start"

485

assert response_start["status"] == 200

486

```

487

488

## Integration Testing

489

490

Test complete application workflows and integrations.

491

492

### Full Application Test

493

494

```python { .api }

495

import pytest

496

from blacksheep.testing import TestClient

497

498

# Complete application for integration testing

499

integration_app = Application()

500

501

# Add CORS

502

integration_app.use_cors(allow_origins="*")

503

504

# Add authentication

505

auth_strategy = integration_app.use_authentication()

506

507

# Add routes

508

@integration_app.get("/")

509

async def home():

510

return json({"app": "integration_test", "version": "1.0"})

511

512

@integration_app.get("/health")

513

async def health_check():

514

# Simulate health check logic

515

return json({"status": "healthy", "timestamp": time.time()})

516

517

@integration_app.post("/api/process")

518

async def process_data(data: FromJSON[dict]):

519

# Simulate data processing

520

result = {

521

"processed": True,

522

"input_size": len(str(data.value)),

523

"output": f"Processed: {data.value}"

524

}

525

return json(result)

526

527

class TestIntegrationWorkflow:

528

529

@pytest.fixture

530

def client(self):

531

return TestClient(integration_app)

532

533

@pytest.mark.asyncio

534

async def test_complete_workflow(self, client):

535

# Test health check

536

response = await client.get("/health")

537

assert response.status == 200

538

health = await response.json()

539

assert health["status"] == "healthy"

540

541

# Test main endpoint

542

response = await client.get("/")

543

assert response.status == 200

544

info = await response.json()

545

assert info["app"] == "integration_test"

546

547

# Test data processing

548

test_data = {"items": [1, 2, 3], "name": "test"}

549

response = await client.post("/api/process", json=test_data)

550

assert response.status == 200

551

552

result = await response.json()

553

assert result["processed"] is True

554

assert result["input_size"] > 0

555

556

@pytest.mark.asyncio

557

async def test_cors_headers(self, client):

558

# Test CORS preflight

559

response = await client.options(

560

"/api/process",

561

headers={"Origin": "https://example.com"}

562

)

563

564

# Check CORS headers

565

cors_origin = response.headers.get_first(b"Access-Control-Allow-Origin")

566

assert cors_origin == b"*"

567

568

@pytest.mark.asyncio

569

async def test_error_handling(self, client):

570

# Test 404

571

response = await client.get("/nonexistent")

572

assert response.status == 404

573

574

# Test invalid JSON

575

response = await client.post(

576

"/api/process",

577

content=TextContent("invalid json"),

578

headers={"Content-Type": "application/json"}

579

)

580

# Should handle parsing error appropriately

581

```

582

583

## Performance Testing

584

585

Basic performance testing with the test client.

586

587

### Load Testing

588

589

```python { .api }

590

import asyncio

591

import time

592

from statistics import mean, stdev

593

594

async def performance_test():

595

client = TestClient(app)

596

597

# Warm up

598

for _ in range(10):

599

await client.get("/")

600

601

# Performance test

602

iterations = 100

603

start_times = []

604

605

for i in range(iterations):

606

start = time.time()

607

response = await client.get("/")

608

end = time.time()

609

610

assert response.status == 200

611

start_times.append((end - start) * 1000) # Convert to ms

612

613

# Calculate statistics

614

avg_time = mean(start_times)

615

std_dev = stdev(start_times) if len(start_times) > 1 else 0

616

min_time = min(start_times)

617

max_time = max(start_times)

618

619

print(f"Performance Results ({iterations} iterations):")

620

print(f" Average: {avg_time:.2f}ms")

621

print(f" Std Dev: {std_dev:.2f}ms")

622

print(f" Min: {min_time:.2f}ms")

623

print(f" Max: {max_time:.2f}ms")

624

625

# Assert performance threshold

626

assert avg_time < 50 # Under 50ms average

627

628

# Concurrent request testing

629

async def concurrent_test():

630

client = TestClient(app)

631

632

async def make_request():

633

response = await client.get("/")

634

return response.status

635

636

# Run 20 concurrent requests

637

tasks = [make_request() for _ in range(20)]

638

results = await asyncio.gather(*tasks)

639

640

# All should succeed

641

assert all(status == 200 for status in results)

642

print(f"Concurrent test: {len(results)} requests completed successfully")

643

644

asyncio.run(performance_test())

645

asyncio.run(concurrent_test())

646

```

647

648

## Test Configuration

649

650

Configure testing environment and fixtures.

651

652

### Test Configuration

653

654

```python { .api }

655

import pytest

656

import os

657

from blacksheep import Application

658

from blacksheep.testing import TestClient

659

660

# Test configuration

661

@pytest.fixture(scope="session")

662

def test_config():

663

return {

664

"DEBUG": True,

665

"TESTING": True,

666

"DATABASE_URL": "sqlite:///:memory:",

667

"SECRET_KEY": "test-secret-key"

668

}

669

670

@pytest.fixture

671

def app_with_config(test_config):

672

"""Create application with test configuration"""

673

app = Application(debug=test_config["DEBUG"])

674

675

# Configure for testing

676

app.configuration = test_config

677

678

# Add test routes

679

@app.get("/config")

680

async def get_config():

681

return json({"debug": app.debug})

682

683

return app

684

685

@pytest.fixture

686

def client(app_with_config):

687

"""Test client with configured application"""

688

return TestClient(app_with_config)

689

690

# Environment-specific tests

691

@pytest.mark.asyncio

692

async def test_debug_mode(client):

693

response = await client.get("/config")

694

data = await response.json()

695

assert data["debug"] is True

696

697

# Skip tests based on conditions

698

@pytest.mark.skipif(os.getenv("CI") is None, reason="Only run in CI")

699

async def test_ci_specific():

700

# Test that only runs in CI environment

701

pass

702

```

703

704

BlackSheep's testing utilities provide comprehensive support for testing all aspects of your web application, from simple unit tests to complex integration scenarios, ensuring your application works correctly and performs well.