or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-routing.mdconfiguration.mddto.mdexceptions.mdhttp-handlers.mdindex.mdmiddleware.mdopenapi.mdplugins.mdrequest-response.mdsecurity.mdtesting.mdwebsocket.md

testing.mddocs/

0

# Testing

1

2

Test client implementations for testing Litestar applications. Litestar provides comprehensive testing utilities including synchronous and asynchronous test clients with support for HTTP requests, WebSocket connections, and integration testing.

3

4

## Capabilities

5

6

### Test Clients

7

8

Test clients provide a convenient way to test Litestar applications without running a full server.

9

10

```python { .api }

11

class TestClient:

12

def __init__(

13

self,

14

app: Litestar,

15

*,

16

base_url: str = "http://testserver",

17

backend: Literal["asyncio", "trio"] = "asyncio",

18

backend_options: dict[str, Any] | None = None,

19

cookies: httpx.Cookies | dict[str, str] | None = None,

20

headers: dict[str, str] | None = None,

21

follow_redirects: bool = False,

22

**kwargs: Any,

23

):

24

"""

25

Synchronous test client for Litestar applications.

26

27

Parameters:

28

- app: Litestar application instance

29

- base_url: Base URL for requests

30

- backend: Async backend to use

31

- backend_options: Backend-specific options

32

- cookies: Default cookies for requests

33

- headers: Default headers for requests

34

- follow_redirects: Whether to follow redirects automatically

35

- **kwargs: Additional httpx.Client options

36

"""

37

38

# HTTP methods

39

def get(self, url: str, **kwargs: Any) -> httpx.Response:

40

"""Send GET request."""

41

42

def post(self, url: str, **kwargs: Any) -> httpx.Response:

43

"""Send POST request."""

44

45

def put(self, url: str, **kwargs: Any) -> httpx.Response:

46

"""Send PUT request."""

47

48

def patch(self, url: str, **kwargs: Any) -> httpx.Response:

49

"""Send PATCH request."""

50

51

def delete(self, url: str, **kwargs: Any) -> httpx.Response:

52

"""Send DELETE request."""

53

54

def head(self, url: str, **kwargs: Any) -> httpx.Response:

55

"""Send HEAD request."""

56

57

def options(self, url: str, **kwargs: Any) -> httpx.Response:

58

"""Send OPTIONS request."""

59

60

def request(self, method: str, url: str, **kwargs: Any) -> httpx.Response:

61

"""Send request with specified method."""

62

63

# Context manager support

64

def __enter__(self) -> TestClient:

65

"""Enter test client context."""

66

67

def __exit__(self, *args: Any) -> None:

68

"""Exit test client context."""

69

70

class AsyncTestClient:

71

def __init__(

72

self,

73

app: Litestar,

74

*,

75

base_url: str = "http://testserver",

76

backend: Literal["asyncio", "trio"] = "asyncio",

77

backend_options: dict[str, Any] | None = None,

78

cookies: httpx.Cookies | dict[str, str] | None = None,

79

headers: dict[str, str] | None = None,

80

follow_redirects: bool = False,

81

**kwargs: Any,

82

):

83

"""

84

Asynchronous test client for Litestar applications.

85

86

Parameters: Same as TestClient

87

"""

88

89

# Async HTTP methods

90

async def get(self, url: str, **kwargs: Any) -> httpx.Response:

91

"""Send GET request asynchronously."""

92

93

async def post(self, url: str, **kwargs: Any) -> httpx.Response:

94

"""Send POST request asynchronously."""

95

96

async def put(self, url: str, **kwargs: Any) -> httpx.Response:

97

"""Send PUT request asynchronously."""

98

99

async def patch(self, url: str, **kwargs: Any) -> httpx.Response:

100

"""Send PATCH request asynchronously."""

101

102

async def delete(self, url: str, **kwargs: Any) -> httpx.Response:

103

"""Send DELETE request asynchronously."""

104

105

async def head(self, url: str, **kwargs: Any) -> httpx.Response:

106

"""Send HEAD request asynchronously."""

107

108

async def options(self, url: str, **kwargs: Any) -> httpx.Response:

109

"""Send OPTIONS request asynchronously."""

110

111

async def request(self, method: str, url: str, **kwargs: Any) -> httpx.Response:

112

"""Send request with specified method asynchronously."""

113

114

# Async context manager support

115

async def __aenter__(self) -> AsyncTestClient:

116

"""Enter async test client context."""

117

118

async def __aexit__(self, *args: Any) -> None:

119

"""Exit async test client context."""

120

```

121

122

### WebSocket Test Session

123

124

Test utilities for WebSocket connections.

125

126

```python { .api }

127

class WebSocketTestSession:

128

def __init__(self, client: TestClient | AsyncTestClient):

129

"""

130

WebSocket test session.

131

132

Parameters:

133

- client: Test client instance

134

"""

135

136

async def __aenter__(self) -> WebSocketTestSession:

137

"""Enter WebSocket test session."""

138

139

async def __aexit__(self, *args: Any) -> None:

140

"""Exit WebSocket test session."""

141

142

async def send_text(self, data: str) -> None:

143

"""Send text data over WebSocket."""

144

145

async def send_bytes(self, data: bytes) -> None:

146

"""Send binary data over WebSocket."""

147

148

async def send_json(self, data: Any) -> None:

149

"""Send JSON data over WebSocket."""

150

151

async def receive_text(self) -> str:

152

"""Receive text data from WebSocket."""

153

154

async def receive_bytes(self) -> bytes:

155

"""Receive binary data from WebSocket."""

156

157

async def receive_json(self) -> Any:

158

"""Receive JSON data from WebSocket."""

159

160

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

161

"""Close the WebSocket connection."""

162

```

163

164

### Test Client Factory Functions

165

166

Convenience functions for creating test clients.

167

168

```python { .api }

169

def create_test_client(

170

route_handlers: ControllerRouterHandler | Sequence[ControllerRouterHandler],

171

*,

172

backend: Literal["asyncio", "trio"] = "asyncio",

173

**litestar_kwargs: Any,

174

) -> TestClient:

175

"""

176

Create a synchronous test client with route handlers.

177

178

Parameters:

179

- route_handlers: Route handlers to test

180

- backend: Async backend to use

181

- **litestar_kwargs: Additional Litestar constructor arguments

182

183

Returns:

184

Configured TestClient instance

185

"""

186

187

async def create_async_test_client(

188

route_handlers: ControllerRouterHandler | Sequence[ControllerRouterHandler],

189

*,

190

backend: Literal["asyncio", "trio"] = "asyncio",

191

**litestar_kwargs: Any,

192

) -> AsyncTestClient:

193

"""

194

Create an asynchronous test client with route handlers.

195

196

Parameters:

197

- route_handlers: Route handlers to test

198

- backend: Async backend to use

199

- **litestar_kwargs: Additional Litestar constructor arguments

200

201

Returns:

202

Configured AsyncTestClient instance

203

"""

204

```

205

206

### Request Factory

207

208

Factory for creating test request objects.

209

210

```python { .api }

211

class RequestFactory:

212

def __init__(self, app: Litestar | None = None):

213

"""

214

Request factory for creating test requests.

215

216

Parameters:

217

- app: Litestar application instance

218

"""

219

220

def get(

221

self,

222

path: str = "/",

223

*,

224

headers: dict[str, str] | None = None,

225

query_params: dict[str, str] | None = None,

226

**kwargs: Any,

227

) -> Request:

228

"""Create a GET request."""

229

230

def post(

231

self,

232

path: str = "/",

233

*,

234

headers: dict[str, str] | None = None,

235

json: Any = None,

236

data: dict[str, Any] | None = None,

237

**kwargs: Any,

238

) -> Request:

239

"""Create a POST request."""

240

241

def put(

242

self,

243

path: str = "/",

244

**kwargs: Any,

245

) -> Request:

246

"""Create a PUT request."""

247

248

def patch(

249

self,

250

path: str = "/",

251

**kwargs: Any,

252

) -> Request:

253

"""Create a PATCH request."""

254

255

def delete(

256

self,

257

path: str = "/",

258

**kwargs: Any,

259

) -> Request:

260

"""Create a DELETE request."""

261

262

def websocket(

263

self,

264

path: str = "/",

265

*,

266

headers: dict[str, str] | None = None,

267

query_params: dict[str, str] | None = None,

268

**kwargs: Any,

269

) -> WebSocket:

270

"""Create a WebSocket connection."""

271

```

272

273

### Subprocess Test Clients

274

275

Test clients that run the application in a separate process for true integration testing.

276

277

```python { .api }

278

def subprocess_sync_client(

279

app: Litestar,

280

*,

281

port: int = 0,

282

host: str = "127.0.0.1",

283

**kwargs: Any,

284

) -> TestClient:

285

"""

286

Create a test client that runs the app in a subprocess.

287

288

Parameters:

289

- app: Litestar application

290

- port: Port to bind to (0 for random port)

291

- host: Host to bind to

292

- **kwargs: Additional test client options

293

294

Returns:

295

TestClient connected to subprocess server

296

"""

297

298

def subprocess_async_client(

299

app: Litestar,

300

*,

301

port: int = 0,

302

host: str = "127.0.0.1",

303

**kwargs: Any,

304

) -> AsyncTestClient:

305

"""

306

Create an async test client that runs the app in a subprocess.

307

308

Parameters:

309

- app: Litestar application

310

- port: Port to bind to (0 for random port)

311

- host: Host to bind to

312

- **kwargs: Additional test client options

313

314

Returns:

315

AsyncTestClient connected to subprocess server

316

"""

317

```

318

319

## Usage Examples

320

321

### Basic HTTP Testing

322

323

```python

324

import pytest

325

from litestar import Litestar, get, post

326

from litestar.testing import TestClient

327

from litestar.status_codes import HTTP_200_OK, HTTP_201_CREATED

328

329

@get("/health")

330

def health_check() -> dict[str, str]:

331

return {"status": "healthy"}

332

333

@post("/users")

334

def create_user(data: dict) -> dict:

335

return {"id": 123, "name": data["name"]}

336

337

app = Litestar(route_handlers=[health_check, create_user])

338

339

def test_health_check():

340

with TestClient(app=app) as client:

341

response = client.get("/health")

342

assert response.status_code == HTTP_200_OK

343

assert response.json() == {"status": "healthy"}

344

345

def test_create_user():

346

with TestClient(app=app) as client:

347

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

348

assert response.status_code == HTTP_201_CREATED

349

assert response.json() == {"id": 123, "name": "Alice"}

350

```

351

352

### Async Testing

353

354

```python

355

import pytest

356

from litestar.testing import AsyncTestClient

357

358

@pytest.mark.asyncio

359

async def test_async_health_check():

360

async with AsyncTestClient(app=app) as client:

361

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

362

assert response.status_code == HTTP_200_OK

363

assert response.json() == {"status": "healthy"}

364

365

@pytest.mark.asyncio

366

async def test_async_create_user():

367

async with AsyncTestClient(app=app) as client:

368

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

369

assert response.status_code == HTTP_201_CREATED

370

assert response.json() == {"id": 123, "name": "Bob"}

371

```

372

373

### WebSocket Testing

374

375

```python

376

from litestar import websocket, WebSocket

377

from litestar.testing import create_test_client

378

379

@websocket("/ws")

380

async def websocket_handler(websocket: WebSocket) -> None:

381

await websocket.accept()

382

message = await websocket.receive_text()

383

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

384

await websocket.close()

385

386

def test_websocket():

387

with create_test_client(route_handlers=[websocket_handler]) as client:

388

with client.websocket_connect("/ws") as websocket:

389

websocket.send_text("Hello WebSocket")

390

data = websocket.receive_text()

391

assert data == "Echo: Hello WebSocket"

392

```

393

394

### Authentication Testing

395

396

```python

397

from litestar.security.jwt import JWTAuth

398

from litestar.testing import create_test_client

399

400

# JWT auth setup (simplified)

401

jwt_auth = JWTAuth(

402

token_secret="test-secret",

403

retrieve_user_handler=lambda token, connection: {"id": 1, "name": "Test User"},

404

)

405

406

@get("/profile")

407

def get_profile(request: Request) -> dict:

408

return {"user": request.user}

409

410

def test_authenticated_endpoint():

411

app = Litestar(

412

route_handlers=[get_profile],

413

on_app_init=[jwt_auth.on_app_init],

414

)

415

416

with TestClient(app=app) as client:

417

# Get token

418

token = jwt_auth.create_token(identifier="1")

419

420

# Make authenticated request

421

response = client.get(

422

"/profile",

423

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

424

)

425

426

assert response.status_code == HTTP_200_OK

427

assert response.json() == {"user": {"id": 1, "name": "Test User"}}

428

429

def test_unauthenticated_request():

430

with TestClient(app=app) as client:

431

response = client.get("/profile")

432

assert response.status_code == 401

433

```

434

435

### Database Testing with Fixtures

436

437

```python

438

import pytest

439

from litestar import Litestar, get, post, Dependency

440

from litestar.testing import create_async_test_client

441

from unittest.mock import AsyncMock

442

443

# Mock database

444

class MockDatabase:

445

def __init__(self):

446

self.users = {}

447

self.next_id = 1

448

449

async def create_user(self, name: str) -> dict:

450

user = {"id": self.next_id, "name": name}

451

self.users[self.next_id] = user

452

self.next_id += 1

453

return user

454

455

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

456

return self.users.get(user_id)

457

458

@pytest.fixture

459

def mock_db():

460

return MockDatabase()

461

462

def create_app(db: MockDatabase) -> Litestar:

463

@post("/users")

464

async def create_user(data: dict, db: MockDatabase = Dependency(lambda: db)) -> dict:

465

return await db.create_user(data["name"])

466

467

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

468

async def get_user(user_id: int, db: MockDatabase = Dependency(lambda: db)) -> dict:

469

user = await db.get_user(user_id)

470

if not user:

471

raise NotFoundException("User not found")

472

return user

473

474

return Litestar(route_handlers=[create_user, get_user])

475

476

@pytest.mark.asyncio

477

async def test_user_crud(mock_db):

478

app = create_app(mock_db)

479

480

async with create_async_test_client(app) as client:

481

# Create user

482

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

483

assert response.status_code == HTTP_201_CREATED

484

user = response.json()

485

assert user["name"] == "Alice"

486

user_id = user["id"]

487

488

# Get user

489

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

490

assert response.status_code == HTTP_200_OK

491

assert response.json() == user

492

493

# Get non-existent user

494

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

495

assert response.status_code == 404

496

```

497

498

### File Upload Testing

499

500

```python

501

from litestar import post, Request

502

from litestar.testing import TestClient

503

import io

504

505

@post("/upload")

506

async def upload_file(request: Request) -> dict:

507

form_data = await request.form()

508

uploaded_file = form_data["file"]

509

return {

510

"filename": uploaded_file.filename,

511

"size": len(await uploaded_file.read()),

512

}

513

514

def test_file_upload():

515

with TestClient(app=Litestar(route_handlers=[upload_file])) as client:

516

file_content = b"test file content"

517

files = {"file": ("test.txt", io.BytesIO(file_content), "text/plain")}

518

519

response = client.post("/upload", files=files)

520

assert response.status_code == HTTP_200_OK

521

assert response.json() == {

522

"filename": "test.txt",

523

"size": len(file_content)

524

}

525

```

526

527

### Custom Headers and Cookies Testing

528

529

```python

530

def test_custom_headers():

531

@get("/headers")

532

def get_headers(request: Request) -> dict:

533

return {"custom_header": request.headers.get("X-Custom-Header")}

534

535

with TestClient(app=Litestar(route_handlers=[get_headers])) as client:

536

response = client.get(

537

"/headers",

538

headers={"X-Custom-Header": "test-value"}

539

)

540

assert response.json() == {"custom_header": "test-value"}

541

542

def test_cookies():

543

@get("/cookies")

544

def get_cookies(request: Request) -> dict:

545

return {"session_id": request.cookies.get("session_id")}

546

547

with TestClient(app=Litestar(route_handlers=[get_cookies])) as client:

548

response = client.get(

549

"/cookies",

550

cookies={"session_id": "abc123"}

551

)

552

assert response.json() == {"session_id": "abc123"}

553

```

554

555

### Integration Testing with Real Server

556

557

```python

558

from litestar.testing import subprocess_async_client

559

import pytest

560

561

@pytest.mark.asyncio

562

async def test_integration():

563

"""Test with real server running in subprocess."""

564

app = Litestar(route_handlers=[health_check])

565

566

async with subprocess_async_client(app) as client:

567

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

568

assert response.status_code == HTTP_200_OK

569

assert response.json() == {"status": "healthy"}

570

```

571

572

## Types

573

574

```python { .api }

575

# Test client types

576

TestClientBackend = Literal["asyncio", "trio"]

577

578

# HTTP response type from httpx

579

HTTPXResponse = httpx.Response

580

581

# Route handler types for testing

582

ControllerRouterHandler = Controller | Router | BaseRouteHandler

583

584

# Test context managers

585

TestClientContext = TestClient

586

AsyncTestClientContext = AsyncTestClient

587

```