This doc is intended for Truth developers (or curious users) who have experience with the API and are interested in the internals that back it. New users should refer to the main usage docs, the FAQ (including this question about the call chain) and the extension docs.
assertThat
Most Truth assertions use one of its assertThat
methods:
assertThat(colors).containsExactly(RED, GREEN, BLUE);
Each assertThat
method returns a Subject
―here, an IterableSubject
. So
you could imagine this assertThat
method has an implementation like the
following:
public static IterableSubject assertThat(Iterable<?> actual) {
return new IterableSubject(actual);
}
The static method is a cleaner API: It lets users write assertThat(...)
rather
than new IterableSubject(...)
, new StringSubject(...)
, etc. But there is
more to Truth’s API than assertThat
. In the following sections, I’ll explain
the rest of the API.
Other parameters to Subject
A Subject
contains more than just the value under test. Thus, assertThat
doesn’t just delegate to the constructor; it also fills in sensible defaults for
the constructor’s other parameters. You could imagine that its implementation
looks like this:
public static IterableSubject assertThat(Iterable<?> actual) {
return new IterableSubject( // kind of Subject to create
actual, // value under test
throwAssertionErrorStrategy(), // default FailureStrategy
""); // default message
}
This raises a question: How do callers specify values other than the defaults?
The answer is that they use Truth’s full Subject
-builder chain:
expect // FailureStrategy
.withMessage("relocate call did not update location") // message
.about(employees()) // kind of Subject to create
.that(db.get(KURT.id()) // value under test
.hasLocation(MTV);
And in fact, even assertThat
is implemented with such a call chain, not with
my hypothetical direct call to the constructor (a
constructor that is likewise slightly different than my hypothetical example).
The intermediate objects in the chain are, depending on the exact methods
called, instances of StandardSubjectBuilder
, SimpleSubjectBuilder
, and
CustomSubjectBuilder
. But the interesting thing isn’t the class names so
much as the order in which the values are specified.
Order of chained calls
Why did we choose this order? I’ll go over the steps of the chain in order, explaining why they appear where they do.
FailureStrategy
Quick refresher: The failure strategy tells Truth what to do when a check fails.
The most common strategy is assert_
, which means to throw an
AssertionError
, but others exist (e.g., expect
, assume
).
It’s natural to put the strategy at the beginning of the chain. This makes all
assertions start with “assert
,” including those that use shortcuts like
assertThat
.
The other natural choice would be to put the message first. But then we’d need a special API for assertions with no custom message. That API is likely to be verbose and to require parameters, which IDEs don’t autocomplete as well as they do with chained method calls:
withoutMessage(assert_())
.that(kurt)
.hasLocation(MTV);
The alternative is to make assert_()
itself work not just as a parameter but
also as a way of starting the chain:
assert_()
.that(kurt)
.hasLocation(MTV);
But if we go that far, it’s probably clearest to make it start the chain in all cases, rather than serve as both a parameter and a standalone entry point.
Additionally, we’d like to permit chaining subjects to add messages but not to
change the FailureStrategy
(except in limited ways). To permit this, we need
to expose an object to subject implemeters that permits setting a message but
has a FailureStrategy
already set. This chaining model,
check(...).withMessage(...)
, fits perfectly with the
assert_().withMessage(...)
model.
There’s one more reason that it’s useful to have an object that supports
withMessage
calls: We may someday add other methods that let callers to add to
the failure message. If so, we’ll want for users to be able to call both
withMessage
and those new methods, so both need to be possible to call in the
middle of the chain. Even today, we support multiple withMessage
calls (with
the expectation that one might happen in a helper method and the other in the
test itself).
Message
The message doesn’t go at the beginning, as discussed above. We chose to make it
the second call, rather than putting it after about
or that
:
Putting it immediately after about
isn’t a good option because many assertion
chains don’t call about
. Additionally, supporting it after about
would mean
supporting it on the CustomSubjectBuilder
type, which would mean giving that
type a self type, an additional complication on a type that’s already difficult
to understand.
Putting it immediately after that
isn’t a good option for three main reasons:
First, messages are often long, so they break the flow of the assertion chain.
We’d rather keep them out of the core of the assertion, that(foo).isBar()
.
Second, putting the message immediately after that
would require Subject
to
have a self type. This would add complexity, as already discussed in the context
of CustomSubjectBuilder
. In the case of Subject
, the complexity is
especially large for Subject
classes that permit subtypes. Note also that
users are likely to expect “Subject<S>
” to mean “a Subject
for assertions on
type S
” rather than “a Subject
whose own type is S
.”
Third, Subject
instances would need a way to create new instances of
themselves with a message added. This may be doable, probably by passing a
Subject.Factory
parameter to the Subject
constructor. But it would further
complicate things, again especially in the presence of subclasses.
Note that the message can’t come after the assertion itself because, if the
assertion throws an exception, the withMessage
call would never happen. (We
could delay throwing the exception until the withMessage
call, but that would
require users to call withMessage
even when they don’t want a message. And if
they forget to call it, their tests will wrongly pass.)
Kind of Subject
to create
At this point, the only question is whether the order is that(...).about(...)
or about(...).that(...)
. The latter keeps the core of the assertion together:
that(foo).isBar()
. It also means that we can include shortcuts like
assert_().that(...)
that directly return a Subject
, not just some kind of
SubjectBuilder
. (An alternative would be for the that
shortcut to return a
Subject
and for Subject
to contain the about
method so that callers can
change the Subject
type afterward. But we’d need a way to check that the given
actual value is compatible with the factory passed to about
. To do so, we’d
have to rely on many Subject
subclasses to declare and use a type parameter
for the actual value.)
Actual value
The only thing left to specify before the assertion itself is the actual value.
As noted above, this keeps the core of the assertion together:
that(foo).isBar()
.
Propagating values through the chain
This is easiest to consider in reverse.
The actual value is passed to the Subject
constructor through the
Subject.Factory
. It doesn’t need to be stored anywhere along the way.
The Subject.Factory
is stored in the SimpleSubjectBuilder
returned by
about
. Once it’s invoked by that
, it’s never needed again.
The interesting parts are the message and FailureStrategy
. Those are bundled
together into an instance of FailureMetadata
. We pass this FailureMetadata
through the chain, all the way into the Subject
and even into any derived
subjects.
Shortcuts
Most users are satisfied with the defaults: no failure message, a
FailureStrategy
of “assert,” and the Subject
types supported by core Truth
and other assertThat
methods. For this reason, we provide various shortcuts: