Most custom Subject
classes follow a simple pattern. However, in
rare cases, you can provide a better API by defining your own that
methods in
a subclass of CustomSubjectBuilder
. We’re aware of two cases in which this
is useful. Both of them affect only users who write a longhand assertion, like
assertWithMessage(...).about(...).that(...)
, as opposed to the shortcut
assertThat(...)
.
Problem: Parameterized Subject
classes
One use case for CustomSubjectBuilder
is a Subject
subclass with its own
type parameters. For example, IterableOfProtosSubject
has a type parameter
<M extends Message>
. Callers need a way to specify the type of M
in
expressions like assertWithMessage(...).about(...).that(...)
. The way to do
that is for the that
method to have a type parameter: <M>
IterableOfProtosSubject<M> that(M message)
. But SimpleSubjectBuilder
’s
that
method doesn’t have a type parameter, so it won’t work.
Problem: Multiple Subject
classes
Another case addresses a problem that’s less severe but more common: what to do
when your library defines multiple Subject
classes. The usual solution is to
declare a Subject.Factory
for each:
import static com.google.common.truth.extension.EmployeeSubject.employees;
import static com.google.common.truth.extension.TeamSubject.teams;
…
assertWithMessage("converter should look up username")
.about(employees())
.that(kurt)
.hasUsername("kak");
…
assertWithMessage("query result should include skip-level employees")
.about(teams())
.that(javaTeam)
.hasMember(kurt);
But this forces some users to import multiple factories. And it adds noise to
the assertion calls: Why draw readers’ attention to the fact that the first
assertion is about an Employee
and the second is about a Team
? We’d like to
be able to write:
import static com.google.common.truth.extension.HumanResourcesTruth.humanResources;
…
assertWithMessage("converter should look up username")
.about(humanResources())
.that(kurt)
.hasUsername("kak");
…
assertWithMessage("query result should include skip-level employees")
.about(humanResources())
.that(javaTeam)
.hasMember(kurt);
Solution: How to declare custom that
methods
Rather than create a Subject.Factory
as described in step 2 of the simple
pattern, do the following:
-
Declare a subclass of
CustomSubjectBuilder
:public final class ProtoSubjectBuilder extends CustomSubjectBuilder {…}
The class must be accessible to the tests that will use it―usually
public
.The class should usually be
final
. (Anyone who wants to provide access to additionalSubject
classes or other behavior can define a separateCustomSubjectBuilder
or other API to expose it.) -
Declare a constructor that takes a
FailureMetadata
:ProtoSubjectBuilder(FailureMetadata metadata) { super(metadata); }
The constructor need not be visible to users. They will create instances through a factory you’ll create.
-
Declare one or more
that
methods:public <M extends Message> IterableOfProtosSubject<M> that( @Nullable Iterable<M> actual) { return new IterableOfProtosSubject<>(metadata(), actual); }
The methods must be accessible to the tests that will use them―usually
public
.(You may need to make your
Subject
constructor package-private so that it is accessible from the builder.) -
Declare a static factory method that returns a
CustomSubjectBuilder.Factory
.public static CustomSubjectBuilder.Factory<ProtoSubjectBuilder> iterablesOfProtos() { return ProtoSubjectBuilder::new; }
The
CustomSubjectBuilder.Factory
should usually be implemented with a method reference, as shown above. Even if it’s not, your method should declare a return type ofCustomSubjectBuilder.Factory<YourType>
, not your implementation class.The static factory method must be accessible to the tests that will use it―typically,
public
.We recommend naming this method in the plural form (e.g.,
EmployeeSubject.employees()
,PersonSubject.people()
, etc.).We recommend putting this method on your
Subject
class itself. Or, if your library defines multipleSubject
subclasses, you may wish to create a singleCustomSubjectBuilder
with multiplethat
methods, one for each. If you go that direction, you’ll probably create a single class (likeProtoTruth
) that contains theprotos()
factory method that supports all the types. (And you’ll use this same class to define all yourassertThat
methods.)Now users can treat your
CustomSubjectBuilder.Factory
just like aSubject.Factory
:import static com.google.common.truth.extension.proto.ProtoTruth.protos; … assertWithMessage("parser should have used last of multiple values") .about(protos()) .that(parser.parse(bytesWithMultipleValues)) .isEqualTo(expected);
(But, as with the
Subject.Factory
approach, most users will use yourassertThat
shortcut instead.)