or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application.mdcommands.mdexceptions.mdindex.mdio.mdstyling.mdtesting.mdui.md

testing.mddocs/

0

# Testing Utilities

1

2

The testing utilities provide comprehensive tools for testing CLI commands and applications with simulated input/output, assertion helpers, and isolated test environments. These utilities enable thorough testing of command behavior without requiring real console interaction.

3

4

## Capabilities

5

6

### Command Testing

7

8

Test individual commands in isolation with controlled input and output.

9

10

```python { .api }

11

class CommandTester:

12

def __init__(self, command: Command) -> None:

13

"""

14

Create a tester for a specific command.

15

16

Args:

17

command (Command): Command instance to test

18

"""

19

20

def execute(self, args: str = "", inputs: str | None = None,

21

interactive: bool | None = None, verbosity: Verbosity | None = None,

22

decorated: bool | None = None) -> int:

23

"""

24

Execute the command with simulated input.

25

26

Args:

27

args (str): Command arguments and options as string

28

inputs (str | None): Simulated user input responses

29

interactive (bool | None): Override interactive mode detection

30

verbosity (Verbosity | None): Set specific verbosity level

31

decorated (bool | None): Override decoration detection

32

33

Returns:

34

int: Command exit code

35

"""

36

37

@property

38

def command(self) -> Command:

39

"""Get the command being tested."""

40

41

@property

42

def io(self) -> IO:

43

"""Get the IO interface used for testing."""

44

45

def get_display(self, normalize: bool = False) -> str:

46

"""

47

Get the output display content.

48

49

Args:

50

normalize (bool): Whether to normalize line endings

51

52

Returns:

53

str: Output content from the command execution

54

"""

55

56

def get_error_output(self, normalize: bool = False) -> str:

57

"""

58

Get the error output content.

59

60

Args:

61

normalize (bool): Whether to normalize line endings

62

63

Returns:

64

str: Error output content from the command execution

65

"""

66

67

def get_status_code(self) -> int:

68

"""

69

Get the last execution status code.

70

71

Returns:

72

int: Exit code from last execution

73

"""

74

```

75

76

### Application Testing

77

78

Test complete applications with multiple commands and routing.

79

80

```python { .api }

81

class ApplicationTester:

82

def __init__(self, application: Application) -> None:

83

"""

84

Create a tester for an application.

85

86

Args:

87

application (Application): Application instance to test

88

"""

89

90

def execute(self, args: str = "", inputs: str | None = None,

91

interactive: bool | None = None, verbosity: Verbosity | None = None,

92

decorated: bool | None = None) -> int:

93

"""

94

Execute application commands with simulated input.

95

96

Args:

97

args (str): Full command line including command name and arguments

98

inputs (str | None): Simulated user input responses

99

interactive (bool | None): Override interactive mode detection

100

verbosity (Verbosity | None): Set specific verbosity level

101

decorated (bool | None): Override decoration detection

102

103

Returns:

104

int: Application exit code

105

"""

106

107

@property

108

def application(self) -> Application:

109

"""Get the application being tested."""

110

111

@property

112

def io(self) -> IO:

113

"""Get the IO interface used for testing."""

114

115

def get_display(self, normalize: bool = False) -> str:

116

"""

117

Get the output display content.

118

119

Args:

120

normalize (bool): Whether to normalize line endings

121

122

Returns:

123

str: Output content from the application execution

124

"""

125

126

def get_error_output(self, normalize: bool = False) -> str:

127

"""

128

Get the error output content.

129

130

Args:

131

normalize (bool): Whether to normalize line endings

132

133

Returns:

134

str: Error output content from the application execution

135

"""

136

137

def get_status_code(self) -> int:

138

"""

139

Get the last execution status code.

140

141

Returns:

142

int: Exit code from last execution

143

"""

144

```

145

146

## Usage Examples

147

148

### Basic Command Testing

149

150

```python

151

import pytest

152

from cleo.testers.command_tester import CommandTester

153

from your_app.commands import GreetCommand

154

155

def test_greet_command_basic():

156

command = GreetCommand()

157

tester = CommandTester(command)

158

159

# Test with no arguments

160

exit_code = tester.execute()

161

assert exit_code == 0

162

assert "Hello" in tester.get_display()

163

164

def test_greet_command_with_name():

165

command = GreetCommand()

166

tester = CommandTester(command)

167

168

# Test with name argument

169

exit_code = tester.execute("John")

170

assert exit_code == 0

171

assert "Hello John" in tester.get_display()

172

173

def test_greet_command_with_yell_option():

174

command = GreetCommand()

175

tester = CommandTester(command)

176

177

# Test with --yell flag

178

exit_code = tester.execute("John --yell")

179

assert exit_code == 0

180

assert "HELLO JOHN" in tester.get_display()

181

```

182

183

### Testing Command Output and Formatting

184

185

```python

186

def test_status_command_output():

187

command = StatusCommand()

188

tester = CommandTester(command)

189

190

exit_code = tester.execute("--verbose")

191

output = tester.get_display()

192

193

# Test formatted output

194

assert exit_code == 0

195

assert "System Status:" in output

196

assert "Service 1: Running" in output

197

assert "Service 2: Stopped" in output

198

199

# Test verbose output only appears with flag

200

tester.execute("")

201

normal_output = tester.get_display()

202

assert len(output) > len(normal_output)

203

204

def test_error_output():

205

command = ProcessCommand()

206

tester = CommandTester(command)

207

208

# Test error conditions

209

exit_code = tester.execute("nonexistent-file.txt")

210

assert exit_code == 1

211

212

error_output = tester.get_error_output()

213

assert "File not found" in error_output

214

```

215

216

### Testing Interactive Commands

217

218

```python

219

def test_interactive_confirmation():

220

command = DeleteCommand()

221

tester = CommandTester(command)

222

223

# Test with "yes" input

224

exit_code = tester.execute("important-file.txt", inputs="yes\n")

225

assert exit_code == 0

226

assert "File deleted" in tester.get_display()

227

228

# Test with "no" input

229

exit_code = tester.execute("important-file.txt", inputs="no\n")

230

assert exit_code == 1

231

assert "Operation cancelled" in tester.get_display()

232

233

def test_interactive_questions():

234

command = ConfigCommand()

235

tester = CommandTester(command)

236

237

# Simulate multiple user inputs

238

inputs = "myproject\n8080\nyes\nproduction\n"

239

exit_code = tester.execute("", inputs=inputs)

240

241

assert exit_code == 0

242

output = tester.get_display()

243

assert "Project: myproject" in output

244

assert "Port: 8080" in output

245

assert "Environment: production" in output

246

247

def test_choice_questions():

248

command = SetupCommand()

249

tester = CommandTester(command)

250

251

# Test single choice

252

exit_code = tester.execute("", inputs="2\n") # Select second option

253

assert exit_code == 0

254

255

# Test invalid choice handling

256

exit_code = tester.execute("", inputs="invalid\n1\n") # Invalid then valid

257

assert exit_code == 0

258

assert "Invalid choice" in tester.get_display()

259

```

260

261

### Testing Command Arguments and Options

262

263

```python

264

def test_argument_validation():

265

command = ProcessCommand()

266

tester = CommandTester(command)

267

268

# Test missing required argument

269

exit_code = tester.execute("")

270

assert exit_code == 1

271

assert "Not enough arguments" in tester.get_error_output()

272

273

# Test valid arguments

274

exit_code = tester.execute("input.txt output.txt")

275

assert exit_code == 0

276

277

def test_option_parsing():

278

command = BuildCommand()

279

tester = CommandTester(command)

280

281

# Test short options

282

exit_code = tester.execute("-v -o dist/")

283

assert exit_code == 0

284

285

# Test long options

286

exit_code = tester.execute("--verbose --output-dir=dist/")

287

assert exit_code == 0

288

289

# Test option values

290

tester.execute("--format json")

291

assert command.option("format") == "json"

292

```

293

294

### Application-Level Testing

295

296

```python

297

def test_application_command_routing():

298

from your_app.application import create_application

299

300

app = create_application()

301

tester = ApplicationTester(app)

302

303

# Test help command

304

exit_code = tester.execute("help")

305

assert exit_code == 0

306

assert "Available commands:" in tester.get_display()

307

308

# Test list command

309

exit_code = tester.execute("list")

310

assert exit_code == 0

311

output = tester.get_display()

312

assert "process" in output # Our custom command should be listed

313

assert "config" in output

314

315

def test_application_command_execution():

316

app = create_application()

317

tester = ApplicationTester(app)

318

319

# Test executing specific commands

320

exit_code = tester.execute("process input.txt --format json")

321

assert exit_code == 0

322

323

exit_code = tester.execute("config --interactive", inputs="yes\nproduction\n")

324

assert exit_code == 0

325

326

def test_command_not_found():

327

app = create_application()

328

tester = ApplicationTester(app)

329

330

exit_code = tester.execute("nonexistent-command")

331

assert exit_code == 1

332

assert "Command not found" in tester.get_error_output()

333

```

334

335

### Testing Verbosity Levels

336

337

```python

338

def test_verbosity_levels():

339

command = DiagnosticCommand()

340

tester = CommandTester(command)

341

342

# Test quiet mode

343

exit_code = tester.execute("--quiet")

344

quiet_output = tester.get_display()

345

assert exit_code == 0

346

assert len(quiet_output.strip()) == 0 # No output in quiet mode

347

348

# Test normal verbosity

349

exit_code = tester.execute("")

350

normal_output = tester.get_display()

351

assert "Basic info" in normal_output

352

assert "Debug info" not in normal_output

353

354

# Test verbose mode

355

exit_code = tester.execute("--verbose")

356

verbose_output = tester.get_display()

357

assert "Basic info" in verbose_output

358

assert "Detailed info" in verbose_output

359

assert "Debug info" not in verbose_output

360

361

# Test debug mode

362

exit_code = tester.execute("-vvv")

363

debug_output = tester.get_display()

364

assert "Basic info" in debug_output

365

assert "Detailed info" in debug_output

366

assert "Debug info" in debug_output

367

```

368

369

### Testing Table Output

370

371

```python

372

def test_table_output():

373

command = ReportCommand()

374

tester = CommandTester(command)

375

376

exit_code = tester.execute("")

377

output = tester.get_display()

378

379

# Check table structure

380

assert "Name" in output # Header

381

assert "Status" in output # Header

382

assert "Service A" in output # Data row

383

assert "Running" in output # Data row

384

385

# Check table formatting (borders, alignment)

386

lines = output.split('\n')

387

header_line = next(line for line in lines if "Name" in line)

388

assert "│" in header_line or "|" in header_line # Table borders

389

390

def test_progress_output():

391

command = ProcessCommand()

392

tester = CommandTester(command)

393

394

exit_code = tester.execute("--show-progress")

395

output = tester.get_display()

396

397

# Progress bars create temporary output that gets cleared

398

# Test for completion message instead

399

assert "Processing complete" in output

400

assert exit_code == 0

401

```

402

403

### Test Fixtures and Helpers

404

405

```python

406

import pytest

407

import tempfile

408

import os

409

410

@pytest.fixture

411

def temp_dir():

412

"""Create a temporary directory for test files."""

413

with tempfile.TemporaryDirectory() as tmpdir:

414

yield tmpdir

415

416

@pytest.fixture

417

def sample_config_file(temp_dir):

418

"""Create a sample configuration file."""

419

config_path = os.path.join(temp_dir, "config.json")

420

with open(config_path, 'w') as f:

421

f.write('{"database": {"host": "localhost", "port": 5432}}')

422

return config_path

423

424

def test_command_with_files(sample_config_file):

425

command = LoadConfigCommand()

426

tester = CommandTester(command)

427

428

exit_code = tester.execute(f"--config {sample_config_file}")

429

assert exit_code == 0

430

assert "Configuration loaded" in tester.get_display()

431

432

class TestCommandHelper:

433

"""Helper class for common test patterns."""

434

435

@staticmethod

436

def execute_command(command_class, args="", inputs=None):

437

"""Execute a command and return tester for assertions."""

438

command = command_class()

439

tester = CommandTester(command)

440

exit_code = tester.execute(args, inputs)

441

return tester, exit_code

442

443

@staticmethod

444

def assert_success(tester, exit_code, expected_output=None):

445

"""Assert command executed successfully."""

446

assert exit_code == 0

447

if expected_output:

448

assert expected_output in tester.get_display()

449

450

@staticmethod

451

def assert_error(tester, exit_code, expected_error=None):

452

"""Assert command failed with error."""

453

assert exit_code != 0

454

if expected_error:

455

assert expected_error in tester.get_error_output()

456

457

# Usage of helper

458

def test_with_helper():

459

tester, exit_code = TestCommandHelper.execute_command(

460

GreetCommand,

461

"John --yell"

462

)

463

TestCommandHelper.assert_success(tester, exit_code, "HELLO JOHN")

464

```

465

466

### Parameterized Testing

467

468

```python

469

@pytest.mark.parametrize("name,expected", [

470

("John", "Hello John"),

471

("Alice", "Hello Alice"),

472

("", "Hello"),

473

])

474

def test_greet_variations(name, expected):

475

command = GreetCommand()

476

tester = CommandTester(command)

477

478

exit_code = tester.execute(name)

479

assert exit_code == 0

480

assert expected in tester.get_display()

481

482

@pytest.mark.parametrize("args,should_succeed", [

483

("valid-file.txt", True),

484

("", False), # Missing required argument

485

("nonexistent.txt", False), # File doesn't exist

486

])

487

def test_file_processing(args, should_succeed):

488

command = ProcessFileCommand()

489

tester = CommandTester(command)

490

491

exit_code = tester.execute(args)

492

493

if should_succeed:

494

assert exit_code == 0

495

assert "Success" in tester.get_display()

496

else:

497

assert exit_code != 0

498

```