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 additionalSubjectclasses or other behavior can define a separateCustomSubjectBuilderor 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
thatmethods: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
Subjectconstructor 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.Factoryshould 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
Subjectclass itself. Or, if your library defines multipleSubjectsubclasses, you may wish to create a singleCustomSubjectBuilderwith multiplethatmethods, 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 yourassertThatmethods.)Now users can treat your
CustomSubjectBuilder.Factoryjust 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.Factoryapproach, most users will use yourassertThatshortcut instead.)