or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cache-clients.mdcache-controller.mdcache-transports.mdhttp-headers.mdindex.mdserializers.mdstorage-backends.mdtesting-utilities.md

testing-utilities.mddocs/

0

# Testing Utilities

1

2

Mock connection pools and transports for unit testing HTTP caching behavior. These utilities allow you to test caching logic without making actual network requests.

3

4

## Capabilities

5

6

### Mock Synchronous Transport

7

8

Mock transport for testing synchronous HTTP client caching behavior.

9

10

```python { .api }

11

class MockTransport(httpx.BaseTransport):

12

def handle_request(self, request: httpx.Request) -> httpx.Response:

13

"""Handle request by returning pre-configured mock response"""

14

15

def add_responses(self, responses: list[httpx.Response]) -> None:

16

"""

17

Add mock responses to be returned in order.

18

19

Parameters:

20

- responses: List of httpx.Response objects to return

21

"""

22

```

23

24

**Usage Examples:**

25

26

```python

27

import httpx

28

import hishel

29

30

# Create mock responses

31

response1 = httpx.Response(200, json={"data": "first"})

32

response2 = httpx.Response(200, json={"data": "second"})

33

34

# Set up mock transport

35

mock_transport = hishel.MockTransport()

36

mock_transport.add_responses([response1, response2])

37

38

# Use with cache client for testing

39

cache_transport = hishel.CacheTransport(transport=mock_transport)

40

with httpx.Client(transport=cache_transport) as client:

41

# First request uses first mock response

42

resp1 = client.get("https://api.example.com/data")

43

assert resp1.json() == {"data": "first"}

44

45

# Second request should be served from cache

46

resp2 = client.get("https://api.example.com/data")

47

assert resp2.json() == {"data": "first"} # Same as first (cached)

48

```

49

50

### Mock Asynchronous Transport

51

52

Mock transport for testing asynchronous HTTP client caching behavior.

53

54

```python { .api }

55

class MockAsyncTransport(httpx.AsyncBaseTransport):

56

async def handle_async_request(self, request: httpx.Request) -> httpx.Response:

57

"""Handle async request by returning pre-configured mock response"""

58

59

def add_responses(self, responses: list[httpx.Response]) -> None:

60

"""

61

Add mock responses to be returned in order.

62

63

Parameters:

64

- responses: List of httpx.Response objects to return

65

"""

66

```

67

68

**Usage Examples:**

69

70

```python

71

import httpx

72

import hishel

73

import asyncio

74

75

async def test_async_caching():

76

# Create mock responses with cache headers

77

response = httpx.Response(

78

200,

79

headers={"cache-control": "max-age=3600"},

80

json={"data": "cached"}

81

)

82

83

# Set up mock async transport

84

mock_transport = hishel.MockAsyncTransport()

85

mock_transport.add_responses([response])

86

87

# Use with async cache client

88

cache_transport = hishel.AsyncCacheTransport(transport=mock_transport)

89

async with httpx.AsyncClient(transport=cache_transport) as client:

90

# First request uses mock response

91

resp1 = await client.get("https://api.example.com/data")

92

assert resp1.json() == {"data": "cached"}

93

94

# Second request served from cache (no additional mock response needed)

95

resp2 = await client.get("https://api.example.com/data")

96

assert resp2.json() == {"data": "cached"}

97

98

asyncio.run(test_async_caching())

99

```

100

101

### Mock Connection Pools

102

103

Lower-level mock connection pools for testing httpcore-level caching.

104

105

```python { .api }

106

class MockConnectionPool:

107

def handle_request(self, request: httpcore.Request) -> httpcore.Response:

108

"""Handle httpcore request with mock response"""

109

110

def add_responses(self, responses: list[httpcore.Response]) -> None:

111

"""

112

Add mock httpcore responses.

113

114

Parameters:

115

- responses: List of httpcore.Response objects

116

"""

117

118

class MockAsyncConnectionPool:

119

async def handle_async_request(self, request: httpcore.Request) -> httpcore.Response:

120

"""Handle async httpcore request with mock response"""

121

122

def add_responses(self, responses: list[httpcore.Response]) -> None:

123

"""

124

Add mock httpcore responses.

125

126

Parameters:

127

- responses: List of httpcore.Response objects

128

"""

129

```

130

131

**Usage Examples:**

132

133

```python

134

import httpcore

135

import hishel

136

137

# Create mock httpcore responses

138

response = httpcore.Response(

139

200,

140

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

141

content=b'{"data": "test"}'

142

)

143

144

# Test with mock connection pool

145

mock_pool = hishel.MockConnectionPool()

146

mock_pool.add_responses([response])

147

148

# Use for lower-level testing

149

request = httpcore.Request(b"GET", b"https://api.example.com/data")

150

response = mock_pool.handle_request(request)

151

assert response.status == 200

152

```

153

154

## Testing Cache Behavior

155

156

### Test Cache Hit/Miss

157

158

```python

159

import httpx

160

import hishel

161

import pytest

162

163

def test_cache_hit_miss():

164

# Mock response with cache headers

165

cached_response = httpx.Response(

166

200,

167

headers={"cache-control": "max-age=3600", "etag": '"abc123"'},

168

json={"data": "original"}

169

)

170

171

# Fresh response for revalidation

172

fresh_response = httpx.Response(

173

200,

174

headers={"cache-control": "max-age=3600", "etag": '"def456"'},

175

json={"data": "updated"}

176

)

177

178

mock_transport = hishel.MockTransport()

179

mock_transport.add_responses([cached_response, fresh_response])

180

181

storage = hishel.InMemoryStorage()

182

cache_transport = hishel.CacheTransport(

183

transport=mock_transport,

184

storage=storage

185

)

186

187

with httpx.Client(transport=cache_transport) as client:

188

# First request - cache miss

189

resp1 = client.get("https://api.example.com/data")

190

assert resp1.json() == {"data": "original"}

191

192

# Second request - cache hit

193

resp2 = client.get("https://api.example.com/data")

194

assert resp2.json() == {"data": "original"} # Served from cache

195

196

# Verify only one network request was made

197

# (second response in mock is unused)

198

```

199

200

### Test Cache Revalidation

201

202

```python

203

import httpx

204

import hishel

205

206

def test_cache_revalidation():

207

# Original response

208

original_response = httpx.Response(

209

200,

210

headers={"cache-control": "max-age=0", "etag": '"version1"'},

211

json={"version": 1}

212

)

213

214

# 304 Not Modified response

215

not_modified_response = httpx.Response(

216

304,

217

headers={"cache-control": "max-age=3600", "etag": '"version1"'}

218

)

219

220

mock_transport = hishel.MockTransport()

221

mock_transport.add_responses([original_response, not_modified_response])

222

223

cache_transport = hishel.CacheTransport(transport=mock_transport)

224

225

with httpx.Client(transport=cache_transport) as client:

226

# First request

227

resp1 = client.get("https://api.example.com/data")

228

assert resp1.json() == {"version": 1}

229

230

# Second request triggers revalidation due to max-age=0

231

resp2 = client.get("https://api.example.com/data")

232

assert resp2.json() == {"version": 1} # Same content (304 response)

233

assert resp2.status_code == 200 # But status is 200 (from cache)

234

```

235

236

### Test Different Storage Backends

237

238

```python

239

import httpx

240

import hishel

241

import tempfile

242

import pytest

243

244

@pytest.mark.parametrize("storage_class", [

245

hishel.InMemoryStorage,

246

hishel.FileStorage,

247

])

248

def test_storage_backends(storage_class):

249

response = httpx.Response(

250

200,

251

headers={"cache-control": "max-age=3600"},

252

json={"backend": storage_class.__name__}

253

)

254

255

mock_transport = hishel.MockTransport()

256

mock_transport.add_responses([response])

257

258

# Configure storage based on type

259

if storage_class == hishel.FileStorage:

260

with tempfile.TemporaryDirectory() as tmpdir:

261

storage = storage_class(base_path=tmpdir)

262

else:

263

storage = storage_class()

264

265

cache_transport = hishel.CacheTransport(

266

transport=mock_transport,

267

storage=storage

268

)

269

270

with httpx.Client(transport=cache_transport) as client:

271

resp1 = client.get("https://api.example.com/data")

272

resp2 = client.get("https://api.example.com/data") # From cache

273

274

assert resp1.json() == resp2.json()

275

```

276

277

### Test Cache Control Directives

278

279

```python

280

import httpx

281

import hishel

282

283

def test_no_cache_directive():

284

# Response with no-cache directive

285

response = httpx.Response(

286

200,

287

headers={"cache-control": "no-cache"},

288

json={"directive": "no-cache"}

289

)

290

291

revalidation_response = httpx.Response(

292

200,

293

headers={"cache-control": "max-age=3600"},

294

json={"directive": "revalidated"}

295

)

296

297

mock_transport = hishel.MockTransport()

298

mock_transport.add_responses([response, revalidation_response])

299

300

cache_transport = hishel.CacheTransport(transport=mock_transport)

301

302

with httpx.Client(transport=cache_transport) as client:

303

# First request

304

resp1 = client.get("https://api.example.com/data")

305

assert resp1.json() == {"directive": "no-cache"}

306

307

# Second request should revalidate due to no-cache

308

resp2 = client.get("https://api.example.com/data")

309

assert resp2.json() == {"directive": "revalidated"}

310

```

311

312

## Test Utilities

313

314

### Custom Test Storage

315

316

```python

317

import hishel

318

319

class TestStorage(hishel.BaseStorage):

320

"""Test storage that tracks operations"""

321

322

def __init__(self):

323

super().__init__()

324

self.stored_keys = []

325

self.retrieved_keys = []

326

self.cache = {}

327

328

def store(self, key, response, request, metadata=None):

329

self.stored_keys.append(key)

330

self.cache[key] = (response, request, metadata)

331

332

def retrieve(self, key):

333

self.retrieved_keys.append(key)

334

return self.cache.get(key)

335

336

def remove(self, key):

337

self.cache.pop(key, None)

338

339

def update_metadata(self, key, response, request, metadata):

340

if key in self.cache:

341

self.cache[key] = (response, request, metadata)

342

343

def close(self):

344

pass

345

346

# Use in tests

347

def test_with_custom_storage():

348

test_storage = TestStorage()

349

350

response = httpx.Response(200, json={"test": True})

351

mock_transport = hishel.MockTransport()

352

mock_transport.add_responses([response])

353

354

cache_transport = hishel.CacheTransport(

355

transport=mock_transport,

356

storage=test_storage

357

)

358

359

with httpx.Client(transport=cache_transport) as client:

360

client.get("https://api.example.com/data")

361

client.get("https://api.example.com/data") # Cache hit

362

363

assert len(test_storage.stored_keys) == 1

364

assert len(test_storage.retrieved_keys) == 1

365

```