or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

array-assertions.mdcollection-assertions.mdcore-assertions.mdcustom-assertions.mdexception-assertions.mdindex.mdjava8-assertions.mdmap-assertions.mdnumeric-assertions.mdstring-assertions.mdtesting-utilities.md

custom-assertions.mddocs/

0

# Custom Assertions and Extensions

1

2

Extension mechanisms for creating custom subject types, correspondence-based comparisons, and advanced assertion patterns.

3

4

## Capabilities

5

6

### Subject Factory Pattern

7

8

The primary mechanism for extending Truth with custom subjects.

9

10

```java { .api }

11

/**

12

* Given a factory for some Subject class, returns a builder whose that(actual) method

13

* creates instances of that class.

14

* @param factory factory for creating custom subjects

15

*/

16

public static <S extends Subject, T> SimpleSubjectBuilder<S, T> assertAbout(Subject.Factory<S, T> factory);

17

18

/**

19

* Factory interface for creating Subject instances.

20

*/

21

public interface Subject.Factory<SubjectT extends Subject, ActualT> {

22

/**

23

* Creates a new Subject.

24

* @param metadata failure metadata for context

25

* @param actual the value under test

26

*/

27

SubjectT createSubject(FailureMetadata metadata, ActualT actual);

28

}

29

```

30

31

**Custom Subject Example:**

32

33

```java

34

// Define a custom subject for Person objects

35

public class PersonSubject extends Subject {

36

private final Person actual;

37

38

protected PersonSubject(FailureMetadata metadata, Person actual) {

39

super(metadata, actual);

40

this.actual = actual;

41

}

42

43

// Custom assertion methods

44

public void hasName(String expectedName) {

45

check("getName()").that(actual.getName()).isEqualTo(expectedName);

46

}

47

48

public void hasAge(int expectedAge) {

49

check("getAge()").that(actual.getAge()).isEqualTo(expectedAge);

50

}

51

52

public void isAdult() {

53

check("getAge()").that(actual.getAge()).isAtLeast(18);

54

}

55

56

// Factory for creating PersonSubject instances

57

public static Subject.Factory<PersonSubject, Person> persons() {

58

return PersonSubject::new;

59

}

60

}

61

62

// Usage

63

Person person = new Person("Alice", 25);

64

assertAbout(persons()).that(person).hasName("Alice");

65

assertAbout(persons()).that(person).hasAge(25);

66

assertAbout(persons()).that(person).isAdult();

67

```

68

69

### Custom Subject Builder Pattern

70

71

Advanced extension mechanism for complex custom subjects.

72

73

```java { .api }

74

/**

75

* A generic, advanced method of extension of Truth to new types.

76

* @param factory factory for creating custom subject builders

77

*/

78

public static <CustomSubjectBuilderT extends CustomSubjectBuilder> CustomSubjectBuilderT assertAbout(

79

CustomSubjectBuilder.Factory<CustomSubjectBuilderT> factory);

80

81

/**

82

* Base class for advanced custom subject builders.

83

*/

84

public abstract class CustomSubjectBuilder {

85

/**

86

* Factory interface for creating CustomSubjectBuilder instances.

87

*/

88

public interface Factory<CustomSubjectBuilderT extends CustomSubjectBuilder> {

89

/**

90

* Creates a new CustomSubjectBuilder.

91

* @param metadata failure metadata for context

92

*/

93

CustomSubjectBuilderT createSubjectBuilder(FailureMetadata metadata);

94

}

95

}

96

```

97

98

### Correspondence-Based Comparisons

99

100

Flexible comparison mechanism for custom element matching logic.

101

102

```java { .api }

103

/**

104

* Abstract class defining custom comparison logic between actual and expected elements.

105

*/

106

public abstract class Correspondence<A, E> {

107

/**

108

* Returns true if the actual and expected elements correspond according to this correspondence.

109

* @param actual the actual element

110

* @param expected the expected element

111

*/

112

public abstract boolean compare(A actual, E expected);

113

114

/**

115

* Returns a description of the difference between actual and expected elements, or null.

116

* @param actual the actual element

117

* @param expected the expected element

118

*/

119

public String formatDiff(A actual, E expected) {

120

return null; // Default implementation

121

}

122

}

123

```

124

125

#### Static Factory Methods for Correspondence

126

127

```java { .api }

128

/**

129

* Returns a Correspondence that compares elements using the given binary predicate.

130

* @param predicate the binary predicate for comparison

131

* @param description human-readable description of the correspondence

132

*/

133

public static <A, E> Correspondence<A, E> from(BinaryPredicate<A, E> predicate, String description);

134

135

/**

136

* Returns a Correspondence that compares actual elements to expected elements by

137

* transforming the actual elements using the given function.

138

* @param actualTransform function to transform actual elements

139

* @param description human-readable description of the transformation

140

*/

141

public static <A, E> Correspondence<A, E> transforming(Function<A, E> actualTransform, String description);

142

143

/**

144

* Returns a Correspondence for comparing Double values with the given tolerance.

145

* @param tolerance the maximum allowed difference

146

*/

147

public static Correspondence<Double, Double> tolerance(double tolerance);

148

```

149

150

**Correspondence Examples:**

151

152

```java

153

import com.google.common.truth.Correspondence;

154

import java.util.function.Function;

155

156

// Case-insensitive string comparison

157

Correspondence<String, String> CASE_INSENSITIVE =

158

Correspondence.from(String::equalsIgnoreCase, "ignoring case");

159

160

List<String> actualNames = Arrays.asList("Alice", "BOB", "charlie");

161

List<String> expectedNames = Arrays.asList("alice", "bob", "Charlie");

162

163

assertThat(actualNames)

164

.comparingElementsUsing(CASE_INSENSITIVE)

165

.containsExactlyElementsIn(expectedNames);

166

167

// Transform-based comparison

168

Correspondence<Person, String> BY_NAME =

169

Correspondence.transforming(Person::getName, "by name");

170

171

List<Person> people = Arrays.asList(new Person("Alice"), new Person("Bob"));

172

assertThat(people)

173

.comparingElementsUsing(BY_NAME)

174

.containsExactly("Alice", "Bob");

175

176

// Numeric tolerance

177

Correspondence<Double, Double> TOLERANCE = Correspondence.tolerance(0.01);

178

List<Double> measurements = Arrays.asList(1.001, 2.002, 3.003);

179

List<Double> expected = Arrays.asList(1.0, 2.0, 3.0);

180

181

assertThat(measurements)

182

.comparingElementsUsing(TOLERANCE)

183

.containsExactlyElementsIn(expected);

184

```

185

186

### Enhanced Correspondence with Diff Formatting

187

188

```java { .api }

189

/**

190

* Returns a new Correspondence that formats diffs using the given formatter.

191

* @param formatter the formatter for creating diff descriptions

192

*/

193

public Correspondence<A, E> formattingDiffsUsing(DiffFormatter<? super A, ? super E> formatter);

194

195

/**

196

* Interface for formatting differences between actual and expected values.

197

*/

198

@FunctionalInterface

199

public interface DiffFormatter<A, E> {

200

/**

201

* Returns a description of the difference between actual and expected values.

202

* @param actual the actual value

203

* @param expected the expected value

204

*/

205

String formatDiff(A actual, E expected);

206

}

207

```

208

209

**Diff Formatting Example:**

210

211

```java

212

// Custom correspondence with detailed diff formatting

213

Correspondence<Person, Person> BY_PERSON_FIELDS =

214

Correspondence.from((actual, expected) ->

215

Objects.equals(actual.getName(), expected.getName()) &&

216

Objects.equals(actual.getAge(), expected.getAge()), "by name and age")

217

.formattingDiffsUsing((actual, expected) ->

218

String.format("expected: [name=%s, age=%d], but was: [name=%s, age=%d]",

219

expected.getName(), expected.getAge(),

220

actual.getName(), actual.getAge()));

221

222

List<Person> actual = Arrays.asList(new Person("Alice", 25));

223

List<Person> expected = Arrays.asList(new Person("Alice", 30));

224

225

// This will provide detailed diff information in the failure message

226

assertThat(actual)

227

.comparingElementsUsing(BY_PERSON_FIELDS)

228

.containsExactlyElementsIn(expected);

229

```

230

231

### Custom Failure Messages with Facts

232

233

```java { .api }

234

/**

235

* Returns a new subject builder with custom failure message context.

236

* @param messageToPrepend message to prepend to failure messages

237

*/

238

public StandardSubjectBuilder withMessage(String messageToPrepend);

239

240

/**

241

* Class representing structured failure information.

242

*/

243

public final class Fact {

244

/**

245

* Creates a fact with a key and value.

246

* @param key the fact key

247

* @param value the fact value

248

*/

249

public static Fact fact(String key, Object value);

250

251

/**

252

* Creates a fact with only a key (no value).

253

* @param key the fact key

254

*/

255

public static Fact simpleFact(String key);

256

}

257

```

258

259

**Custom Subject with Rich Failure Messages:**

260

261

```java

262

public class PersonSubject extends Subject {

263

private final Person actual;

264

265

protected PersonSubject(FailureMetadata metadata, Person actual) {

266

super(metadata, actual);

267

this.actual = actual;

268

}

269

270

public void hasValidEmail() {

271

if (actual.getEmail() == null || !isValidEmail(actual.getEmail())) {

272

failWithActual(

273

fact("expected", "valid email address"),

274

fact("but email was", actual.getEmail()),

275

simpleFact("valid email format: user@domain.com")

276

);

277

}

278

}

279

280

private boolean isValidEmail(String email) {

281

return email.contains("@") && email.contains(".");

282

}

283

}

284

```

285

286

### Extension Best Practices

287

288

**Creating Domain-Specific Subjects:**

289

290

```java

291

// Subject for HTTP Response objects

292

public class HttpResponseSubject extends Subject {

293

private final HttpResponse actual;

294

295

protected HttpResponseSubject(FailureMetadata metadata, HttpResponse actual) {

296

super(metadata, actual);

297

this.actual = actual;

298

}

299

300

public void hasStatus(int expectedStatus) {

301

check("getStatus()").that(actual.getStatus()).isEqualTo(expectedStatus);

302

}

303

304

public void isSuccessful() {

305

int status = actual.getStatus();

306

if (status < 200 || status >= 300) {

307

failWithActual(

308

fact("expected", "successful status (200-299)"),

309

fact("but status was", status)

310

);

311

}

312

}

313

314

public void hasHeader(String name, String value) {

315

check("getHeader(" + name + ")")

316

.that(actual.getHeader(name))

317

.isEqualTo(value);

318

}

319

320

public StringSubject hasBodyThat() {

321

return check("getBody()").that(actual.getBody());

322

}

323

324

// Factory method

325

public static Subject.Factory<HttpResponseSubject, HttpResponse> httpResponses() {

326

return HttpResponseSubject::new;

327

}

328

}

329

330

// Usage

331

HttpResponse response = makeHttpRequest();

332

assertAbout(httpResponses()).that(response).isSuccessful();

333

assertAbout(httpResponses()).that(response).hasStatus(200);

334

assertAbout(httpResponses()).that(response).hasHeader("Content-Type", "application/json");

335

assertAbout(httpResponses()).that(response).hasBodyThat().contains("success");

336

```

337

338

## Types

339

340

```java { .api }

341

/**

342

* Base class for all Truth subjects providing common assertion methods.

343

*/

344

public class Subject {

345

/**

346

* Constructor for use by subclasses.

347

* @param metadata failure metadata containing context information

348

* @param actual the value under test

349

*/

350

protected Subject(FailureMetadata metadata, Object actual);

351

352

/**

353

* Factory interface for creating Subject instances.

354

*/

355

public interface Factory<SubjectT extends Subject, ActualT> {

356

SubjectT createSubject(FailureMetadata metadata, ActualT actual);

357

}

358

359

/**

360

* Creates a derived subject for chaining assertions.

361

* @param format format string for the derived subject description

362

* @param args arguments for the format string

363

*/

364

protected StandardSubjectBuilder check(String format, Object... args);

365

366

/**

367

* Reports a failure with the actual value and additional facts.

368

* @param facts additional facts to include in the failure message

369

*/

370

protected void failWithActual(Fact... facts);

371

}

372

373

/**

374

* Abstract class defining custom comparison logic between actual and expected elements.

375

*/

376

public abstract class Correspondence<A, E> {

377

// Methods documented above

378

}

379

380

/**

381

* Base class for advanced custom subject builders.

382

*/

383

public abstract class CustomSubjectBuilder {

384

/**

385

* Constructor for CustomSubjectBuilder.

386

* @param metadata failure metadata for context

387

*/

388

protected CustomSubjectBuilder(FailureMetadata metadata);

389

390

/**

391

* Factory interface for creating CustomSubjectBuilder instances.

392

*/

393

public interface Factory<CustomSubjectBuilderT extends CustomSubjectBuilder> {

394

CustomSubjectBuilderT createSubjectBuilder(FailureMetadata metadata);

395

}

396

}

397

398

/**

399

* Class representing structured failure information.

400

*/

401

public final class Fact {

402

// Methods documented above

403

}

404

405

/**

406

* Functional interface for binary predicates used in Correspondence.from().

407

*/

408

@FunctionalInterface

409

public interface BinaryPredicate<A, E> {

410

/**

411

* Applies this predicate to the given arguments.

412

* @param actual the actual value

413

* @param expected the expected value

414

*/

415

boolean apply(A actual, E expected);

416

}

417

418

/**

419

* Interface for formatting differences between actual and expected values.

420

*/

421

@FunctionalInterface

422

public interface DiffFormatter<A, E> {

423

/**

424

* Returns a description of the difference between actual and expected values.

425

* @param actual the actual value

426

* @param expected the expected value

427

*/

428

String formatDiff(A actual, E expected);

429

}

430

```