or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-mocking.mdfixtures.mdindex.mdmock-creation.md

mock-creation.mddocs/

0

# Mock Creation and Management

1

2

Advanced mock creation utilities and specialized mock objects for sophisticated testing scenarios. These capabilities extend beyond basic patching to provide spies, stubs, and automatic specification-based mocks.

3

4

## Capabilities

5

6

### Automatic Specification Mocks

7

8

Creates mocks that automatically match the interface of existing objects, providing better type safety and catching interface mismatches during testing.

9

10

```python { .api }

11

def create_autospec(

12

self,

13

spec: Any,

14

spec_set: bool = False,

15

instance: bool = False,

16

**kwargs: Any

17

) -> MockType:

18

"""

19

Create a mock with automatic specification from an existing object.

20

21

Parameters:

22

- spec: Object to use as the specification template

23

- spec_set: If True, only allow access to attributes that exist on spec

24

- instance: If True, mock represents an instance of spec rather than spec itself

25

- **kwargs: Additional arguments passed to mock constructor

26

27

Returns:

28

Mock object that matches the spec's interface

29

"""

30

```

31

32

Usage examples:

33

34

```python

35

class DatabaseClient:

36

def connect(self, host: str, port: int) -> bool:

37

return True

38

39

def query(self, sql: str) -> List[Dict]:

40

return []

41

42

def close(self) -> None:

43

pass

44

45

def test_autospec_class(mocker):

46

# Create mock that matches DatabaseClient interface

47

mock_client = mocker.create_autospec(DatabaseClient)

48

mock_client.connect.return_value = True

49

mock_client.query.return_value = [{'id': 1, 'name': 'test'}]

50

51

# Mock enforces correct method signatures

52

result = mock_client.connect('localhost', 5432) # OK

53

data = mock_client.query('SELECT * FROM users') # OK

54

55

# This would raise AttributeError - method doesn't exist on spec

56

# mock_client.invalid_method()

57

58

mock_client.connect.assert_called_with('localhost', 5432)

59

60

def test_autospec_instance(mocker):

61

# Create mock representing an instance of DatabaseClient

62

mock_instance = mocker.create_autospec(DatabaseClient, instance=True)

63

mock_instance.connect.return_value = True

64

65

# Use like an instance

66

setup_database(mock_instance)

67

68

mock_instance.connect.assert_called()

69

70

def test_autospec_function(mocker):

71

def complex_function(a: int, b: str, c: bool = False) -> Dict:

72

return {'result': a + len(b)}

73

74

# Mock enforces function signature

75

mock_func = mocker.create_autospec(complex_function)

76

mock_func.return_value = {'result': 42}

77

78

# Correct call

79

result = mock_func(10, 'hello', c=True)

80

assert result == {'result': 42}

81

82

# This would raise TypeError - wrong signature

83

# mock_func(10) # Missing required argument 'b'

84

```

85

86

### Spy Objects

87

88

Creates spy objects that call the original method while recording all interactions, perfect for verifying method calls without changing behavior.

89

90

```python { .api }

91

def spy(self, obj: object, name: str) -> MockType:

92

"""

93

Create a spy that calls the original method and records calls.

94

95

The spy maintains the original behavior while providing mock

96

capabilities for verification and introspection.

97

98

Parameters:

99

- obj: Object containing the method to spy on

100

- name: Name of the method to spy on

101

102

Returns:

103

Mock object that wraps the original method

104

105

Spy attributes:

106

- spy_return: Return value of the last call

107

- spy_return_list: List of all return values

108

- spy_exception: Exception from last call (if any)

109

"""

110

```

111

112

Usage examples:

113

114

```python

115

class FileProcessor:

116

def process_file(self, filepath: str) -> Dict:

117

# Actually processes the file

118

with open(filepath, 'r') as f:

119

content = f.read()

120

return {'size': len(content), 'processed': True}

121

122

def test_spy_method_calls(mocker, tmp_path):

123

# Create test file

124

test_file = tmp_path / "test.txt"

125

test_file.write_text("Hello World")

126

127

processor = FileProcessor()

128

129

# Spy on the method - it still works normally

130

spy = mocker.spy(processor, 'process_file')

131

132

# Call the method - file is actually processed

133

result = processor.process_file(str(test_file))

134

135

# Verify the real behavior occurred

136

assert result['size'] == 11

137

assert result['processed'] is True

138

139

# Verify the spy recorded the call

140

spy.assert_called_once_with(str(test_file))

141

assert spy.spy_return == result

142

assert spy.spy_return_list == [result]

143

144

def test_spy_multiple_calls(mocker):

145

calculator = Calculator()

146

spy = mocker.spy(calculator, 'add')

147

148

# Make multiple calls

149

result1 = calculator.add(2, 3) # Returns 5

150

result2 = calculator.add(10, 20) # Returns 30

151

152

# Verify all calls were recorded

153

assert spy.call_count == 2

154

spy.assert_has_calls([

155

mocker.call(2, 3),

156

mocker.call(10, 20)

157

])

158

159

assert spy.spy_return_list == [5, 30]

160

161

def test_spy_exception_handling(mocker):

162

processor = FileProcessor()

163

spy = mocker.spy(processor, 'process_file')

164

165

# Call with invalid file - raises exception

166

with pytest.raises(FileNotFoundError):

167

processor.process_file('nonexistent.txt')

168

169

# Spy recorded the exception

170

assert isinstance(spy.spy_exception, FileNotFoundError)

171

spy.assert_called_once_with('nonexistent.txt')

172

173

async def test_async_spy(mocker):

174

class AsyncProcessor:

175

async def fetch_data(self, url: str) -> Dict:

176

# Actual async operation

177

await asyncio.sleep(0.1)

178

return {'url': url, 'status': 'success'}

179

180

processor = AsyncProcessor()

181

spy = mocker.spy(processor, 'fetch_data')

182

183

# Call the async method

184

result = await processor.fetch_data('https://api.example.com')

185

186

# Verify spy recorded the async call

187

assert result['status'] == 'success'

188

spy.assert_called_once_with('https://api.example.com')

189

assert spy.spy_return == result

190

```

191

192

### Stub Objects

193

194

Creates stub objects that accept any arguments and return configurable values, ideal for callback testing and placeholder implementations.

195

196

```python { .api }

197

def stub(self, name: Optional[str] = None) -> unittest.mock.MagicMock:

198

"""

199

Create a stub method that accepts any arguments.

200

201

Parameters:

202

- name: Optional name for the stub (used in repr)

203

204

Returns:

205

MagicMock configured as a stub

206

"""

207

208

def async_stub(self, name: Optional[str] = None) -> AsyncMockType:

209

"""

210

Create an async stub method that accepts any arguments.

211

212

Parameters:

213

- name: Optional name for the stub (used in repr)

214

215

Returns:

216

AsyncMock configured as a stub

217

"""

218

```

219

220

Usage examples:

221

222

```python

223

def test_callback_with_stub(mocker):

224

# Create a stub for callback testing

225

callback = mocker.stub(name='data_callback')

226

227

# Configure the stub's behavior

228

callback.return_value = 'processed'

229

230

# Use stub in code that expects a callback

231

def process_data(data, callback_fn):

232

result = callback_fn(data)

233

return f"Result: {result}"

234

235

# Test with the stub

236

output = process_data({'id': 1}, callback)

237

238

assert output == "Result: processed"

239

callback.assert_called_once_with({'id': 1})

240

241

def test_multiple_callbacks(mocker):

242

# Create multiple stubs for different callbacks

243

on_start = mocker.stub(name='on_start')

244

on_complete = mocker.stub(name='on_complete')

245

on_error = mocker.stub(name='on_error')

246

247

# Configure different return values

248

on_start.return_value = None

249

on_complete.return_value = 'success'

250

on_error.return_value = None

251

252

# Use in code that takes multiple callbacks

253

processor = DataProcessor()

254

processor.process(

255

data={'items': [1, 2, 3]},

256

on_start=on_start,

257

on_complete=on_complete,

258

on_error=on_error

259

)

260

261

# Verify callback usage

262

on_start.assert_called_once()

263

on_complete.assert_called_once_with('success')

264

on_error.assert_not_called()

265

266

async def test_async_callback_stub(mocker):

267

# Create async stub for async callbacks

268

async_callback = mocker.async_stub(name='async_processor')

269

async_callback.return_value = {'status': 'completed'}

270

271

async def process_async(data, callback):

272

result = await callback(data)

273

return result['status']

274

275

# Test with async stub

276

status = await process_async({'id': 1}, async_callback)

277

278

assert status == 'completed'

279

async_callback.assert_called_once_with({'id': 1})

280

281

def test_flexible_stub_arguments(mocker):

282

# Stub accepts any arguments without complaint

283

flexible_stub = mocker.stub(name='flexible')

284

flexible_stub.return_value = 'ok'

285

286

# All these calls work - stub accepts anything

287

assert flexible_stub() == 'ok'

288

assert flexible_stub('arg') == 'ok'

289

assert flexible_stub('arg1', 'arg2') == 'ok'

290

assert flexible_stub(a=1, b=2) == 'ok'

291

assert flexible_stub('pos', keyword='value') == 'ok'

292

293

# Verify all calls were recorded

294

assert flexible_stub.call_count == 5

295

```

296

297

### Mock Class Aliases

298

299

Direct access to mock classes from the underlying mock module, providing convenient access to all mock types without importing unittest.mock.

300

301

```python { .api }

302

# Mock class aliases (available as mocker attributes)

303

Mock: Type[unittest.mock.Mock]

304

MagicMock: Type[unittest.mock.MagicMock]

305

NonCallableMock: Type[unittest.mock.NonCallableMock]

306

NonCallableMagicMock: Type[unittest.mock.NonCallableMagicMock]

307

PropertyMock: Type[unittest.mock.PropertyMock]

308

AsyncMock: Type[unittest.mock.AsyncMock] # Python 3.8+

309

310

# Mock utilities

311

call: unittest.mock._Call

312

ANY: unittest.mock._AnyComparer

313

DEFAULT: unittest.mock._SentinelObject

314

sentinel: unittest.mock._SentinelObject

315

mock_open: Callable

316

seal: Callable # If available

317

```

318

319

Usage examples:

320

321

```python

322

def test_direct_mock_creation(mocker):

323

# Create mocks directly from mocker attributes

324

magic_mock = mocker.MagicMock()

325

magic_mock.method.return_value = 42

326

327

# Use in your code

328

result = magic_mock.method('arg')

329

assert result == 42

330

331

# Mock is automatically registered for cleanup

332

magic_mock.method.assert_called_once_with('arg')

333

334

def test_property_mock(mocker):

335

class MyClass:

336

@property

337

def value(self):

338

return "real_value"

339

340

obj = MyClass()

341

342

# Mock the property

343

prop_mock = mocker.PropertyMock(return_value="mocked_value")

344

mocker.patch.object(MyClass, 'value', new_callable=lambda: prop_mock)

345

346

assert obj.value == "mocked_value"

347

prop_mock.assert_called_once()

348

349

def test_mock_constants(mocker):

350

# Use ANY to match any argument

351

mock_func = mocker.patch('mymodule.function')

352

353

mymodule.function('some_arg')

354

mock_func.assert_called_with(mocker.ANY)

355

356

# Use call to construct call objects

357

expected_calls = [

358

mocker.call('arg1'),

359

mocker.call('arg2')

360

]

361

362

mymodule.function('arg1')

363

mymodule.function('arg2')

364

365

mock_func.assert_has_calls(expected_calls)

366

367

def test_mock_open(mocker):

368

# Mock file operations

369

mock_file = mocker.mock_open(read_data="file content")

370

mocker.patch('builtins.open', mock_file)

371

372

with open('test.txt', 'r') as f:

373

content = f.read()

374

375

assert content == "file content"

376

mock_file.assert_called_once_with('test.txt', 'r')

377

378

def test_async_mock(mocker):

379

async def async_function():

380

return "async_result"

381

382

# Mock async function

383

mock_async = mocker.AsyncMock(return_value="mocked_result")

384

mocker.patch('mymodule.async_function', mock_async)

385

386

# Test async code

387

import asyncio

388

result = asyncio.run(mymodule.async_function())

389

390

assert result == "mocked_result"

391

mock_async.assert_awaited_once()

392

```

393

394

### Mock Utilities

395

396

Essential utilities for advanced mock assertions and configurations, providing fine-grained control over mock behavior and verification.

397

398

```python { .api }

399

# Call construction and matching

400

call: unittest.mock._Call

401

ANY: unittest.mock._AnyComparer

402

DEFAULT: unittest.mock._SentinelObject

403

sentinel: unittest.mock._SentinelObject

404

mock_open: Callable

405

seal: Callable # If available

406

```

407

408

Usage examples:

409

410

```python

411

def test_call_utility(mocker):

412

"""Using call to verify complex call patterns"""

413

mock_func = mocker.patch('mymodule.complex_function')

414

415

# Make multiple calls

416

mymodule.complex_function('arg1', key='value1')

417

mymodule.complex_function('arg2', key='value2')

418

419

# Verify specific calls

420

expected_calls = [

421

mocker.call('arg1', key='value1'),

422

mocker.call('arg2', key='value2')

423

]

424

mock_func.assert_has_calls(expected_calls)

425

426

def test_any_utility(mocker):

427

"""Using ANY to match flexible arguments"""

428

mock_func = mocker.patch('mymodule.function')

429

430

# Call with any arguments

431

mymodule.function('specific_arg', some_dynamic_value=42)

432

433

# Verify call with partial matching

434

mock_func.assert_called_with('specific_arg', some_dynamic_value=mocker.ANY)

435

436

# Mix specific and ANY arguments

437

mock_func.assert_called_with(mocker.ANY, some_dynamic_value=42)

438

439

def test_default_utility(mocker):

440

"""Using DEFAULT for partial patching"""

441

original_func = mymodule.function

442

443

# Patch with DEFAULT to preserve some behavior

444

mock_func = mocker.patch('mymodule.function')

445

mock_func.side_effect = lambda x: original_func(x) if x > 0 else mocker.DEFAULT

446

447

# DEFAULT returns the default mock behavior for negative values

448

result = mymodule.function(-1) # Returns DEFAULT mock behavior

449

assert isinstance(result, unittest.mock.MagicMock)

450

451

def test_sentinel_utility(mocker):

452

"""Using sentinel for unique sentinel values"""

453

# Create unique sentinel values for testing

454

MISSING = mocker.sentinel.MISSING

455

INVALID = mocker.sentinel.INVALID

456

457

mock_func = mocker.patch('mymodule.function')

458

mock_func.return_value = MISSING

459

460

result = mymodule.function()

461

assert result is MISSING # Identity comparison

462

assert result is not INVALID # Different sentinels

463

464

def test_mock_open_utility(mocker):

465

"""Using mock_open for file operations"""

466

# Mock reading a file

467

file_content = "line 1\nline 2\nline 3"

468

mock_file = mocker.mock_open(read_data=file_content)

469

mocker.patch('builtins.open', mock_file)

470

471

# Test reading

472

with open('test.txt', 'r') as f:

473

content = f.read()

474

lines = f.readlines() # This will be empty due to file pointer

475

476

assert content == file_content

477

mock_file.assert_called_once_with('test.txt', 'r')

478

479

# Test writing

480

mock_write_file = mocker.mock_open()

481

mocker.patch('builtins.open', mock_write_file)

482

483

with open('output.txt', 'w') as f:

484

f.write('test content')

485

486

mock_write_file.assert_called_once_with('output.txt', 'w')

487

mock_write_file().write.assert_called_once_with('test content')

488

489

def test_seal_utility(mocker):

490

"""Using seal to prevent mock attribute creation"""

491

if hasattr(mocker, 'seal'): # seal is only available in Python 3.7+

492

mock_obj = mocker.MagicMock()

493

mock_obj.existing_attr = 'value'

494

495

# Seal the mock to prevent new attribute creation

496

mocker.seal(mock_obj)

497

498

# Existing attributes still work

499

assert mock_obj.existing_attr == 'value'

500

501

# New attributes raise AttributeError

502

with pytest.raises(AttributeError):

503

_ = mock_obj.new_attr

504

```