or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotations.mdassertions.mdassumptions.mdcategories.mdindex.mdmatchers.mdrules.mdstandard-runners.mdtest-runners.mdtheories.md

theories.mddocs/

0

# Theories

1

2

Theories is an experimental feature for property-based testing where tests are theories to be proven against multiple data points. Unlike parameterized tests that explicitly define test data, theories use data points that can be automatically discovered and combined.

3

4

## Capabilities

5

6

### Theory Annotation

7

8

Marks a method as a theory instead of a regular test. Theories are executed once for each valid combination of parameter values.

9

10

```java { .api }

11

/**

12

* Marks a method as a theory

13

* Theories are run with all possible parameter combinations from data points

14

* @param nullsAccepted - Whether null values should be accepted as parameters (default true)

15

*/

16

@Retention(RetentionPolicy.RUNTIME)

17

@Target(ElementType.METHOD)

18

public @interface Theory {

19

boolean nullsAccepted() default true;

20

}

21

```

22

23

**Usage Examples:**

24

25

```java

26

import org.junit.experimental.theories.Theories;

27

import org.junit.experimental.theories.Theory;

28

import org.junit.experimental.theories.DataPoint;

29

import org.junit.runner.RunWith;

30

import static org.junit.Assert.*;

31

import static org.junit.Assume.*;

32

33

@RunWith(Theories.class)

34

public class StringTheoryTest {

35

@DataPoint

36

public static String EMPTY = "";

37

38

@DataPoint

39

public static String SHORT = "a";

40

41

@DataPoint

42

public static String LONG = "hello world";

43

44

@Theory

45

public void lengthIsNonNegative(String str) {

46

assertTrue(str.length() >= 0);

47

}

48

49

@Theory

50

public void concatenationIncreasesLength(String s1, String s2) {

51

int originalLength = s1.length() + s2.length();

52

String concatenated = s1 + s2;

53

assertEquals(originalLength, concatenated.length());

54

}

55

}

56

```

57

58

### DataPoint Annotation

59

60

Marks a field or method as providing a data point for theories. Theories will be executed with each data point.

61

62

```java { .api }

63

/**

64

* Marks a public static field or method as a data point source

65

* Each data point will be used as parameter for theories

66

* @param value - Optional array of names to filter data points

67

* @param ignoredExceptions - Exceptions to ignore when using this data point

68

*/

69

@Retention(RetentionPolicy.RUNTIME)

70

@Target({ElementType.FIELD, ElementType.METHOD})

71

public @interface DataPoint {

72

String[] value() default {};

73

Class<? extends Throwable>[] ignoredExceptions() default {};

74

}

75

```

76

77

**Usage Examples:**

78

79

```java

80

import org.junit.experimental.theories.Theories;

81

import org.junit.experimental.theories.Theory;

82

import org.junit.experimental.theories.DataPoint;

83

import org.junit.runner.RunWith;

84

import static org.junit.Assert.*;

85

86

@RunWith(Theories.class)

87

public class NumberTheoryTest {

88

@DataPoint

89

public static int ZERO = 0;

90

91

@DataPoint

92

public static int POSITIVE = 5;

93

94

@DataPoint

95

public static int NEGATIVE = -5;

96

97

@DataPoint

98

public static int MAX = Integer.MAX_VALUE;

99

100

@Theory

101

public void additionIsCommutative(int a, int b) {

102

assumeTrue(canAdd(a, b)); // Skip if overflow would occur

103

assertEquals(a + b, b + a);

104

}

105

106

@Theory

107

public void absoluteValueIsNonNegative(int x) {

108

assertTrue(Math.abs(x) >= 0);

109

}

110

111

private boolean canAdd(int a, int b) {

112

try {

113

Math.addExact(a, b);

114

return true;

115

} catch (ArithmeticException e) {

116

return false;

117

}

118

}

119

}

120

121

// Data point methods

122

@RunWith(Theories.class)

123

public class MethodDataPointTest {

124

@DataPoint

125

public static String generateEmptyString() {

126

return "";

127

}

128

129

@DataPoint

130

public static String generateShortString() {

131

return "test";

132

}

133

134

@Theory

135

public void everyStringHasDefinedLength(String str) {

136

assertNotNull(str);

137

assertTrue(str.length() >= 0);

138

}

139

}

140

```

141

142

### DataPoints Annotation

143

144

Marks a field or method as providing multiple data points as an array or iterable.

145

146

```java { .api }

147

/**

148

* Marks a public static field or method as providing multiple data points

149

* The field must be an array or Iterable

150

* @param value - Optional array of names to filter data points

151

* @param ignoredExceptions - Exceptions to ignore when using these data points

152

*/

153

@Retention(RetentionPolicy.RUNTIME)

154

@Target({ElementType.FIELD, ElementType.METHOD})

155

public @interface DataPoints {

156

String[] value() default {};

157

Class<? extends Throwable>[] ignoredExceptions() default {};

158

}

159

```

160

161

**Usage Examples:**

162

163

```java

164

import org.junit.experimental.theories.Theories;

165

import org.junit.experimental.theories.Theory;

166

import org.junit.experimental.theories.DataPoints;

167

import org.junit.runner.RunWith;

168

import static org.junit.Assert.*;

169

import static org.junit.Assume.*;

170

171

@RunWith(Theories.class)

172

public class CollectionTheoryTest {

173

@DataPoints

174

public static int[] NUMBERS = {0, 1, -1, 5, -5, 100};

175

176

@DataPoints

177

public static String[] STRINGS = {"", "a", "hello", "world"};

178

179

@Theory

180

public void multiplicationByZeroIsZero(int x) {

181

assertEquals(0, x * 0);

182

}

183

184

@Theory

185

public void stringConcatenationIsNotNull(String s1, String s2) {

186

assertNotNull(s1 + s2);

187

}

188

}

189

190

// DataPoints with List

191

@RunWith(Theories.class)

192

public class ListDataPointsTest {

193

@DataPoints

194

public static List<Integer> PRIMES = Arrays.asList(2, 3, 5, 7, 11, 13);

195

196

@Theory

197

public void primesAreGreaterThanOne(int prime) {

198

assertTrue(prime > 1);

199

}

200

201

@Theory

202

public void productOfPrimesIsComposite(int p1, int p2) {

203

assumeTrue(p1 != p2);

204

int product = p1 * p2;

205

assertTrue(product > p1);

206

assertTrue(product > p2);

207

}

208

}

209

```

210

211

### FromDataPoints Annotation

212

213

References named data points to be used for a specific parameter. Allows selective use of data points.

214

215

```java { .api }

216

/**

217

* Indicates which named data points should be used for a parameter

218

* @param value - Name of the data point group to use

219

*/

220

@Retention(RetentionPolicy.RUNTIME)

221

@Target(ElementType.PARAMETER)

222

public @interface FromDataPoints {

223

String value();

224

}

225

```

226

227

**Usage Examples:**

228

229

```java

230

import org.junit.experimental.theories.Theories;

231

import org.junit.experimental.theories.Theory;

232

import org.junit.experimental.theories.DataPoints;

233

import org.junit.experimental.theories.FromDataPoints;

234

import org.junit.runner.RunWith;

235

import static org.junit.Assert.*;

236

237

@RunWith(Theories.class)

238

public class NamedDataPointsTest {

239

@DataPoints("positiveNumbers")

240

public static int[] POSITIVE = {1, 2, 5, 10, 100};

241

242

@DataPoints("negativeNumbers")

243

public static int[] NEGATIVE = {-1, -2, -5, -10, -100};

244

245

@DataPoints("validStrings")

246

public static String[] VALID = {"hello", "world", "test"};

247

248

@DataPoints("emptyStrings")

249

public static String[] EMPTY = {"", null};

250

251

@Theory

252

public void positiveTimesPositiveIsPositive(

253

@FromDataPoints("positiveNumbers") int a,

254

@FromDataPoints("positiveNumbers") int b

255

) {

256

assertTrue(a * b > 0);

257

}

258

259

@Theory

260

public void positiveTimesNegativeIsNegative(

261

@FromDataPoints("positiveNumbers") int positive,

262

@FromDataPoints("negativeNumbers") int negative

263

) {

264

assertTrue(positive * negative < 0);

265

}

266

267

@Theory

268

public void validStringsAreNotEmpty(

269

@FromDataPoints("validStrings") String str

270

) {

271

assertFalse(str.isEmpty());

272

}

273

}

274

```

275

276

### Theories Runner

277

278

The runner that executes theories with all valid parameter combinations.

279

280

```java { .api }

281

/**

282

* Runner for executing theories

283

* Runs theory methods with all possible parameter combinations

284

*/

285

public class Theories extends BlockJUnit4ClassRunner {

286

/**

287

* Creates Theories runner

288

* @param klass - Test class containing theories

289

* @throws InitializationError if initialization fails

290

*/

291

public Theories(Class<?> klass) throws InitializationError;

292

}

293

```

294

295

**Usage Examples:**

296

297

```java

298

import org.junit.experimental.theories.Theories;

299

import org.junit.experimental.theories.Theory;

300

import org.junit.experimental.theories.DataPoint;

301

import org.junit.runner.RunWith;

302

import static org.junit.Assert.*;

303

304

@RunWith(Theories.class)

305

public class MathTheories {

306

@DataPoint public static int ZERO = 0;

307

@DataPoint public static int ONE = 1;

308

@DataPoint public static int TWO = 2;

309

@DataPoint public static int MINUS_ONE = -1;

310

311

@Theory

312

public void additionIsCommutative(int a, int b) {

313

assertEquals(a + b, b + a);

314

}

315

316

@Theory

317

public void addingZeroDoesNotChange(int x) {

318

assertEquals(x, x + 0);

319

assertEquals(x, 0 + x);

320

}

321

322

@Theory

323

public void multiplyingByOneDoesNotChange(int x) {

324

assertEquals(x, x * 1);

325

assertEquals(x, 1 * x);

326

}

327

}

328

```

329

330

### ParameterSupplier

331

332

Base class for custom parameter suppliers. Allows programmatic generation of data points.

333

334

```java { .api }

335

/**

336

* Supplies values for theory parameters

337

* Extend this to create custom data point sources

338

*/

339

public abstract class ParameterSupplier {

340

/**

341

* Get potential values for a parameter

342

* @param signature - Parameter signature

343

* @return List of potential assignments

344

*/

345

public abstract List<PotentialAssignment> getValueSources(ParameterSignature signature) throws Throwable;

346

}

347

```

348

349

**Usage Examples:**

350

351

```java

352

import org.junit.experimental.theories.ParameterSupplier;

353

import org.junit.experimental.theories.PotentialAssignment;

354

import org.junit.experimental.theories.ParameterSignature;

355

import org.junit.experimental.theories.ParametersSuppliedBy;

356

import org.junit.experimental.theories.Theories;

357

import org.junit.experimental.theories.Theory;

358

import org.junit.runner.RunWith;

359

import java.util.ArrayList;

360

import java.util.List;

361

import static org.junit.Assert.*;

362

363

// Custom supplier for ranges

364

public class BetweenSupplier extends ParameterSupplier {

365

@Override

366

public List<PotentialAssignment> getValueSources(ParameterSignature sig) {

367

Between annotation = sig.getAnnotation(Between.class);

368

List<PotentialAssignment> values = new ArrayList<>();

369

370

for (int i = annotation.first(); i <= annotation.last(); i++) {

371

values.add(PotentialAssignment.forValue(String.valueOf(i), i));

372

}

373

return values;

374

}

375

}

376

377

// Custom annotation

378

@Retention(RetentionPolicy.RUNTIME)

379

@ParametersSuppliedBy(BetweenSupplier.class)

380

public @interface Between {

381

int first();

382

int last();

383

}

384

385

// Usage

386

@RunWith(Theories.class)

387

public class RangeTheoryTest {

388

@Theory

389

public void numbersInRangeAreValid(@Between(first = 1, last = 10) int x) {

390

assertTrue(x >= 1 && x <= 10);

391

}

392

393

@Theory

394

public void sumOfSmallNumbersIsSmall(

395

@Between(first = 1, last = 5) int a,

396

@Between(first = 1, last = 5) int b

397

) {

398

assertTrue(a + b <= 10);

399

}

400

}

401

```

402

403

### ParametersSuppliedBy Annotation

404

405

Specifies a custom parameter supplier for a parameter.

406

407

```java { .api }

408

/**

409

* Indicates which ParameterSupplier should provide values

410

* @param value - ParameterSupplier class

411

*/

412

@Retention(RetentionPolicy.RUNTIME)

413

@Target({ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})

414

public @interface ParametersSuppliedBy {

415

Class<? extends ParameterSupplier> value();

416

}

417

```

418

419

### TestedOn Annotation

420

421

Built-in annotation for specifying integer values to test on.

422

423

```java { .api }

424

/**

425

* Specifies integer values to test a parameter with

426

* @param ints - Array of integer values

427

*/

428

@Retention(RetentionPolicy.RUNTIME)

429

@Target(ElementType.PARAMETER)

430

@ParametersSuppliedBy(TestedOnSupplier.class)

431

public @interface TestedOn {

432

int[] ints();

433

}

434

```

435

436

**Usage Examples:**

437

438

```java

439

import org.junit.experimental.theories.Theories;

440

import org.junit.experimental.theories.Theory;

441

import org.junit.experimental.theories.suppliers.TestedOn;

442

import org.junit.runner.RunWith;

443

import static org.junit.Assert.*;

444

445

@RunWith(Theories.class)

446

public class TestedOnExample {

447

@Theory

448

public void multiplyingByOneDoesNotChange(

449

@TestedOn(ints = {0, 1, 2, -1, -5, 100}) int x

450

) {

451

assertEquals(x, x * 1);

452

}

453

454

@Theory

455

public void squareIsNonNegative(

456

@TestedOn(ints = {0, 1, 2, 5, -1, -5}) int x

457

) {

458

assertTrue(x * x >= 0);

459

}

460

461

@Theory

462

public void divisionByPowerOfTwo(

463

@TestedOn(ints = {0, 16, 32, 64, 128}) int x

464

) {

465

assertTrue(x % 2 == 0);

466

assertTrue(x / 2 <= x);

467

}

468

}

469

```

470

471

## Advanced Theory Patterns

472

473

### Using Assumptions to Constrain Theories

474

475

```java

476

import org.junit.experimental.theories.Theories;

477

import org.junit.experimental.theories.Theory;

478

import org.junit.experimental.theories.DataPoints;

479

import org.junit.runner.RunWith;

480

import static org.junit.Assert.*;

481

import static org.junit.Assume.*;

482

483

@RunWith(Theories.class)

484

public class ConstrainedTheories {

485

@DataPoints

486

public static int[] NUMBERS = {-10, -1, 0, 1, 10, 100};

487

488

@Theory

489

public void divisionWorks(int a, int b) {

490

assumeTrue(b != 0); // Skip when b is zero

491

int result = a / b;

492

assertEquals(a, result * b + (a % b));

493

}

494

495

@Theory

496

public void squareRootOfSquareIsOriginal(int x) {

497

assumeTrue(x >= 0); // Only test non-negative numbers

498

double sqrt = Math.sqrt(x * x);

499

assertEquals(x, sqrt, 0.0001);

500

}

501

502

@Theory

503

public void sortedPairIsOrdered(int a, int b) {

504

assumeTrue(a <= b); // Only test when a <= b

505

int[] sorted = sort(a, b);

506

assertTrue(sorted[0] <= sorted[1]);

507

}

508

}

509

```

510

511

### Combining Theories with Regular Tests

512

513

```java

514

import org.junit.experimental.theories.Theories;

515

import org.junit.experimental.theories.Theory;

516

import org.junit.experimental.theories.DataPoints;

517

import org.junit.Test;

518

import org.junit.runner.RunWith;

519

import static org.junit.Assert.*;

520

521

@RunWith(Theories.class)

522

public class MixedTests {

523

@DataPoints

524

public static int[] NUMBERS = {0, 1, 2, -1};

525

526

// Regular test - runs once

527

@Test

528

public void testSpecificCase() {

529

assertEquals(4, 2 + 2);

530

}

531

532

// Theory - runs for all data point combinations

533

@Theory

534

public void additionIsCommutative(int a, int b) {

535

assertEquals(a + b, b + a);

536

}

537

538

// Another regular test

539

@Test

540

public void testAnotherCase() {

541

assertTrue(true);

542

}

543

}

544

```

545

546

### Complex Object Theories

547

548

```java

549

import org.junit.experimental.theories.Theories;

550

import org.junit.experimental.theories.Theory;

551

import org.junit.experimental.theories.DataPoints;

552

import org.junit.runner.RunWith;

553

import static org.junit.Assert.*;

554

555

@RunWith(Theories.class)

556

public class ComplexObjectTheories {

557

public static class User {

558

String name;

559

int age;

560

561

User(String name, int age) {

562

this.name = name;

563

this.age = age;

564

}

565

}

566

567

@DataPoints

568

public static User[] USERS = {

569

new User("Alice", 25),

570

new User("Bob", 30),

571

new User("Charlie", 35)

572

};

573

574

@DataPoints

575

public static String[] NAMES = {"Alice", "Bob", "Charlie", "David"};

576

577

@Theory

578

public void userNamesAreNotNull(User user) {

579

assertNotNull(user.name);

580

}

581

582

@Theory

583

public void agesArePositive(User user) {

584

assertTrue(user.age > 0);

585

}

586

587

@Theory

588

public void namesAreNotEmpty(String name) {

589

assertFalse(name.isEmpty());

590

}

591

}

592

```

593

594

## Types

595

596

```java { .api }

597

/**

598

* Represents a potential parameter value assignment

599

*/

600

public class PotentialAssignment {

601

/**

602

* Create assignment with value

603

* @param name - Description of value

604

* @param value - The value

605

* @return PotentialAssignment

606

*/

607

public static PotentialAssignment forValue(String name, Object value);

608

609

/**

610

* Get the value

611

* @return Value object

612

*/

613

public Object getValue() throws CouldNotGenerateValueException;

614

615

/**

616

* Get description

617

* @return Description string

618

*/

619

public String getDescription();

620

}

621

622

/**

623

* Describes a parameter's type and annotations

624

*/

625

public class ParameterSignature {

626

/**

627

* Get parameter type

628

* @return Parameter class

629

*/

630

public Class<?> getType();

631

632

/**

633

* Get parameter annotations

634

* @return List of annotations

635

*/

636

public List<Annotation> getAnnotations();

637

638

/**

639

* Get specific annotation

640

* @param annotationType - Annotation class

641

* @return Annotation or null

642

*/

643

public <T extends Annotation> T getAnnotation(Class<T> annotationType);

644

645

/**

646

* Check if parameter has annotation

647

* @param type - Annotation class

648

* @return true if annotation present

649

*/

650

public boolean hasAnnotation(Class<? extends Annotation> type);

651

652

/**

653

* Get parameter name

654

* @return Parameter name

655

*/

656

public String getName();

657

658

/**

659

* Check if type can accept value

660

* @param value - Value to check

661

* @return true if compatible

662

*/

663

public boolean canAcceptValue(Object value);

664

665

/**

666

* Check if type can potentially accept another type

667

* @param type - Type to check

668

* @return true if potentially compatible

669

*/

670

public boolean canPotentiallyAcceptType(Class<?> type);

671

}

672

673

/**

674

* Thrown when parameter value cannot be generated

675

*/

676

public class CouldNotGenerateValueException extends Exception {

677

public CouldNotGenerateValueException();

678

public CouldNotGenerateValueException(Throwable cause);

679

}

680

681

/**

682

* Exception indicating parameterized assertion failure

683

*/

684

public class ParameterizedAssertionError extends AssertionError {

685

public ParameterizedAssertionError(Throwable targetException, String methodName, Object... params);

686

}

687

```

688

689

## Theories vs Parameterized Tests

690

691

| Aspect | Theories | Parameterized Tests |

692

|--------|----------|---------------------|

693

| Purpose | Property-based testing | Data-driven testing |

694

| Data definition | Flexible data points | Explicit parameter sets |

695

| Combinations | Automatic combinations | Manual specification |

696

| Assumptions | Use assumeXxx to skip | Not available |

697

| Use case | General properties | Specific test cases |

698

| Runner | @RunWith(Theories.class) | @RunWith(Parameterized.class) |

699

| Failure reporting | Shows failing combination | Shows failing parameters |

700

701

**When to use Theories:**

702

- Testing general properties that should hold for all inputs

703

- Exploring edge cases automatically

704

- Property-based testing approach

705

- When you want automatic parameter combinations

706

707

**When to use Parameterized:**

708

- Testing specific known scenarios

709

- Explicit test case data

710

- When each parameter set is independent

711

- More readable test output needed

712