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
```