or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

conditional-testing.mdcore-testing.mdindex.mdmain-method-testing.mdmocking.mdtest-callbacks.mdtest-profiles.md

main-method-testing.mddocs/

0

# Main Method Testing

1

2

Quarkus JUnit 5 provides comprehensive support for testing command-line applications that use Quarkus main method execution.

3

4

## Main Method Test Annotations

5

6

### @QuarkusMainTest

7

8

Tests the main method within the same JVM as the test, creating a new in-memory Quarkus application that runs to completion.

9

10

```java { .api }

11

@Target(ElementType.TYPE)

12

@Retention(RetentionPolicy.RUNTIME)

13

@ExtendWith(QuarkusMainTestExtension.class)

14

public @interface QuarkusMainTest {

15

}

16

```

17

18

### @QuarkusMainIntegrationTest

19

20

Tests the main method against built artifacts (JAR, native image, or container), running in a separate process.

21

22

```java { .api }

23

@Target(ElementType.TYPE)

24

@Retention(RetentionPolicy.RUNTIME)

25

@ExtendWith(QuarkusMainIntegrationTestExtension.class)

26

public @interface QuarkusMainIntegrationTest {

27

}

28

```

29

30

## Launch Annotations and Interfaces

31

32

### @Launch Annotation

33

34

Annotation for launching command-line applications with specified arguments and expected exit codes.

35

36

```java { .api }

37

@Target(ElementType.METHOD)

38

@Retention(RetentionPolicy.RUNTIME)

39

public @interface Launch {

40

/**

41

* The program arguments to launch with

42

*/

43

String[] value() default "";

44

45

/**

46

* Expected return code

47

*/

48

int exitCode() default 0;

49

}

50

```

51

52

### QuarkusMainLauncher Interface

53

54

Interface for programmatically launching command-line applications with arbitrary parameters.

55

56

```java { .api }

57

public interface QuarkusMainLauncher {

58

/**

59

* Launch the command line application with the given parameters.

60

*/

61

LaunchResult launch(String... args);

62

}

63

```

64

65

### LaunchResult Interface

66

67

Contains information about a command-line application execution run.

68

69

```java { .api }

70

public interface LaunchResult {

71

/**

72

* Get the command line application standard output as a single string.

73

*/

74

default String getOutput() {

75

return String.join("\n", getOutputStream());

76

}

77

78

/**

79

* Get the command line application error output as a single string.

80

*/

81

default String getErrorOutput() {

82

return String.join("\n", getErrorStream());

83

}

84

85

/**

86

* Echo the command line application standard output to the console.

87

*/

88

default void echoSystemOut() {

89

System.out.println(getOutput());

90

System.out.println();

91

}

92

93

/**

94

* Get the command line application standard output as a list of strings.

95

* Each line of output correspond to a string in the list.

96

*/

97

List<String> getOutputStream();

98

99

/**

100

* Get the command line application error output as a list of strings.

101

* Each line of output correspond to a string in the list.

102

*/

103

List<String> getErrorStream();

104

105

/**

106

* Get the exit code of the application.

107

*/

108

int exitCode();

109

}

110

```

111

112

## Basic Usage Examples

113

114

### Simple Main Method Test

115

116

```java

117

import io.quarkus.test.junit.main.QuarkusMainTest;

118

import io.quarkus.test.junit.main.Launch;

119

import io.quarkus.test.junit.main.LaunchResult;

120

import org.junit.jupiter.api.Test;

121

import static org.junit.jupiter.api.Assertions.*;

122

123

@QuarkusMainTest

124

class CalculatorMainTest {

125

126

@Test

127

@Launch({"add", "5", "3"})

128

void testAddCommand(LaunchResult result) {

129

assertEquals(0, result.exitCode());

130

assertTrue(result.getOutput().contains("Result: 8"));

131

}

132

133

@Test

134

@Launch(value = {"divide", "10", "0"}, exitCode = 1)

135

void testDivisionByZero(LaunchResult result) {

136

assertEquals(1, result.exitCode());

137

assertTrue(result.getErrorOutput().contains("Division by zero"));

138

}

139

140

@Test

141

@Launch({"--help"})

142

void testHelpOutput(LaunchResult result) {

143

assertEquals(0, result.exitCode());

144

String output = result.getOutput();

145

assertTrue(output.contains("Usage:"));

146

assertTrue(output.contains("Commands:"));

147

}

148

}

149

```

150

151

### Programmatic Launch Testing

152

153

```java

154

import io.quarkus.test.junit.main.QuarkusMainTest;

155

import io.quarkus.test.junit.main.QuarkusMainLauncher;

156

import io.quarkus.test.junit.main.LaunchResult;

157

import org.junit.jupiter.api.Test;

158

159

@QuarkusMainTest

160

class FileProcessorMainTest {

161

162

@Test

163

void testFileProcessing(QuarkusMainLauncher launcher) {

164

// Test successful file processing

165

LaunchResult result = launcher.launch("process", "/tmp/input.txt", "/tmp/output.txt");

166

assertEquals(0, result.exitCode());

167

assertTrue(result.getOutput().contains("Processing completed"));

168

169

// Test missing file

170

LaunchResult errorResult = launcher.launch("process", "/nonexistent/file.txt");

171

assertEquals(2, errorResult.exitCode());

172

assertTrue(errorResult.getErrorOutput().contains("File not found"));

173

}

174

175

@Test

176

void testValidationMode(QuarkusMainLauncher launcher) {

177

LaunchResult result = launcher.launch("validate", "--strict", "/tmp/data.xml");

178

179

if (result.exitCode() == 0) {

180

assertTrue(result.getOutput().contains("Validation passed"));

181

} else {

182

assertTrue(result.getErrorOutput().contains("Validation failed"));

183

}

184

}

185

}

186

```

187

188

### Integration Testing

189

190

```java

191

import io.quarkus.test.junit.main.QuarkusMainIntegrationTest;

192

import io.quarkus.test.junit.main.Launch;

193

import io.quarkus.test.junit.main.LaunchResult;

194

import org.junit.jupiter.api.Test;

195

196

@QuarkusMainIntegrationTest

197

class DatabaseMigratorIT {

198

199

@Test

200

@Launch({"migrate", "--url", "jdbc:h2:mem:test"})

201

void testDatabaseMigration(LaunchResult result) {

202

assertEquals(0, result.exitCode());

203

204

List<String> outputLines = result.getOutputStream();

205

assertTrue(outputLines.stream().anyMatch(line ->

206

line.contains("Migration completed successfully")));

207

}

208

209

@Test

210

@Launch(value = {"rollback", "--version", "invalid"}, exitCode = 1)

211

void testInvalidRollback(LaunchResult result) {

212

assertEquals(1, result.exitCode());

213

assertTrue(result.getErrorOutput().contains("Invalid version"));

214

}

215

}

216

```

217

218

## Advanced Testing Patterns

219

220

### Configuration-Dependent Testing

221

222

```java

223

@QuarkusMainTest

224

class ConfigurableAppTest {

225

226

@Test

227

void testWithDifferentConfigs(QuarkusMainLauncher launcher) {

228

// Test with development config

229

LaunchResult devResult = launcher.launch("--profile", "dev", "start");

230

assertEquals(0, devResult.exitCode());

231

232

// Test with production config

233

LaunchResult prodResult = launcher.launch("--profile", "prod", "start");

234

assertEquals(0, prodResult.exitCode());

235

236

// Verify different behaviors

237

assertNotEquals(devResult.getOutput(), prodResult.getOutput());

238

}

239

}

240

```

241

242

### Multi-Command Application Testing

243

244

```java

245

@QuarkusMainTest

246

class CliToolTest {

247

248

@Test

249

void testAllCommands(QuarkusMainLauncher launcher) {

250

// Test create command

251

LaunchResult createResult = launcher.launch("create", "project", "my-app");

252

assertEquals(0, createResult.exitCode());

253

assertTrue(createResult.getOutput().contains("Project created"));

254

255

// Test list command

256

LaunchResult listResult = launcher.launch("list", "projects");

257

assertEquals(0, listResult.exitCode());

258

assertTrue(listResult.getOutput().contains("my-app"));

259

260

// Test delete command

261

LaunchResult deleteResult = launcher.launch("delete", "project", "my-app");

262

assertEquals(0, deleteResult.exitCode());

263

assertTrue(deleteResult.getOutput().contains("Project deleted"));

264

}

265

}

266

```

267

268

### Output Parsing and Validation

269

270

```java

271

@QuarkusMainTest

272

class DataAnalyzerTest {

273

274

@Test

275

@Launch({"analyze", "--format", "json", "/tmp/data.csv"})

276

void testJsonOutput(LaunchResult result) {

277

assertEquals(0, result.exitCode());

278

279

String jsonOutput = result.getOutput();

280

281

// Parse and validate JSON structure

282

ObjectMapper mapper = new ObjectMapper();

283

JsonNode json = mapper.readTree(jsonOutput);

284

285

assertTrue(json.has("summary"));

286

assertTrue(json.has("results"));

287

assertTrue(json.get("results").isArray());

288

}

289

290

@Test

291

@Launch({"analyze", "--format", "csv", "/tmp/data.csv"})

292

void testCsvOutput(LaunchResult result) {

293

assertEquals(0, result.exitCode());

294

295

List<String> lines = result.getOutputStream();

296

297

// Validate CSV format

298

assertTrue(lines.get(0).contains("column1,column2,column3")); // Header

299

assertTrue(lines.size() > 1); // Has data rows

300

301

// Validate each data row

302

lines.stream().skip(1).forEach(line -> {

303

String[] columns = line.split(",");

304

assertEquals(3, columns.length);

305

});

306

}

307

}

308

```

309

310

### Error Handling and Exit Codes

311

312

```java

313

@QuarkusMainTest

314

class ErrorHandlingTest {

315

316

@Test

317

void testErrorConditions(QuarkusMainLauncher launcher) {

318

// Test invalid command

319

LaunchResult invalidCmd = launcher.launch("invalid-command");

320

assertEquals(1, invalidCmd.exitCode());

321

assertTrue(invalidCmd.getErrorOutput().contains("Unknown command"));

322

323

// Test missing required argument

324

LaunchResult missingArg = launcher.launch("process");

325

assertEquals(2, missingArg.exitCode());

326

assertTrue(missingArg.getErrorOutput().contains("Missing required"));

327

328

// Test invalid argument value

329

LaunchResult invalidArg = launcher.launch("process", "--threads", "invalid");

330

assertEquals(3, invalidArg.exitCode());

331

assertTrue(invalidArg.getErrorOutput().contains("Invalid number"));

332

}

333

}

334

```

335

336

## Testing Best Practices

337

338

### Test Organization

339

340

```java

341

// Base test class for shared test logic

342

@QuarkusMainTest

343

class BaseMainTest {

344

345

protected void assertSuccessfulExecution(LaunchResult result) {

346

assertEquals(0, result.exitCode());

347

assertFalse(result.getOutput().trim().isEmpty());

348

}

349

350

protected void assertErrorExecution(LaunchResult result, String expectedError) {

351

assertNotEquals(0, result.exitCode());

352

assertTrue(result.getErrorOutput().contains(expectedError));

353

}

354

}

355

356

// Integration test extends base test

357

@QuarkusMainIntegrationTest

358

class MainIntegrationTest extends BaseMainTest {

359

// Same test methods run against built artifact

360

}

361

```

362

363

### Resource Management

364

365

```java

366

@QuarkusMainTest

367

class ResourceManagedTest {

368

369

private Path tempDir;

370

371

@BeforeEach

372

void setupTempDirectory() throws IOException {

373

tempDir = Files.createTempDirectory("test");

374

}

375

376

@AfterEach

377

void cleanupTempDirectory() throws IOException {

378

Files.walk(tempDir)

379

.sorted(Comparator.reverseOrder())

380

.map(Path::toFile)

381

.forEach(File::delete);

382

}

383

384

@Test

385

void testFileOperation(QuarkusMainLauncher launcher) {

386

Path inputFile = tempDir.resolve("input.txt");

387

Files.write(inputFile, "test data".getBytes());

388

389

LaunchResult result = launcher.launch("process", inputFile.toString());

390

assertEquals(0, result.exitCode());

391

}

392

}

393

```

394

395

### Performance Testing

396

397

```java

398

@QuarkusMainTest

399

class PerformanceTest {

400

401

@Test

402

@Timeout(value = 30, unit = TimeUnit.SECONDS)

403

void testPerformanceRequirement(QuarkusMainLauncher launcher) {

404

long startTime = System.currentTimeMillis();

405

406

LaunchResult result = launcher.launch("heavy-operation", "--size", "1000");

407

assertEquals(0, result.exitCode());

408

409

long duration = System.currentTimeMillis() - startTime;

410

assertTrue(duration < 30000, "Operation took too long: " + duration + "ms");

411

}

412

}

413

```

414

415

## Limitations and Considerations

416

417

### CDI Injection Limitation

418

```java

419

@QuarkusMainTest

420

class MainTestLimitations {

421

422

// ❌ Not supported in main method tests

423

// @Inject

424

// SomeService service;

425

426

@Test

427

void testWithoutInjection(QuarkusMainLauncher launcher) {

428

// Must test through main method execution only

429

LaunchResult result = launcher.launch("command");

430

assertEquals(0, result.exitCode());

431

}

432

}

433

```

434

435

### Application Lifecycle

436

- Each test method starts a new application instance

437

- Application runs to completion before test method continues

438

- Cannot test long-running applications directly (use integration tests instead)

439

440

### Native Image Testing

441

```java

442

// Works with both JVM and native image builds

443

@QuarkusMainIntegrationTest

444

class NativeCompatibleTest {

445

446

@Test

447

@Launch({"quick-command"})

448

void testNativeCompatibility(LaunchResult result) {

449

// Same test works for both JVM and native image

450

assertEquals(0, result.exitCode());

451

}

452

}

453

```