- Correspondence
- Iterable Example
- Map Example
- Multimap Example
- Getting better failure messages
- Protocol buffers
Fuzzy Truth extends Truth to allow you to make assertions about Iterable
,
Map
, and Multimap
subjects, where the values are compared using something
other than object equality. This mechanism is integral to the APIs for comparing
collections of doubles and floats using approximate equality
and of protocol buffers, but the framework is quite general.
You can also use it to improve failure messages while still using object equality.
Correspondence
The primary concept of Fuzzy Truth is a
Correspondence
. A correspondence determines whether an
instance of type A
corresponds in some way to an instance of type E
.
Optionally, it can also describe the difference between two
instances that do not correspond. A Correspondence<A, E>
is used in an
assertion about a collection of elements of type A
(typically the collection
actually returned by the code under test), checking that it contains (or,
occasionally, does not contain) certain expected elements of type E
.
You use a Correspondence
by passing it into a method such as
comparingElementsUsing
,
as shown in the examples below.
Here’s an example correspondence between strings, which tests whether the actual strings contain the expected substrings:
private static final Correspondence<String, String> CONTAINS_SUBSTRING =
Correspondence.from(String::contains, "contains");
Here’s an example correspondence between strings and integers, which tests whether the string parses as the integer:
class ThisTest {
private static final Correspondence<String, Integer> STRING_PARSES_TO_INTEGER =
Correspondence.from(ThisTest::stringParsesToInteger, "parses to");
private static boolean stringParsesToInteger(
@Nullable String actual, @Nullable Integer expected) {
if (actual == null) {
return expected == null;
}
try {
return Integer.decode(actual).equals(expected);
} catch (NumberFormatException e) {
return false;
}
}
// ...tests using STRING_PARSES_TO_INTEGER...
}
The most general factory method for Correspondence
instances is
Correspondence.from
. Other factory methods are
available for convenience in specific cases, such as
Correspondence.transforming
for testing the
elements for equality after some transformation. Here’s an example
correspondence between instances of some MyRecord
class and integers, which
tests whether calling getId
on the record returns the integer:
private static final Correspondence<MyRecord, Integer> RECORD_HAS_ID =
Correspondence.transforming(MyRecord::getId, "has an ID of");
You may want to think about handling of null elements. In the examples above,
STRING_PARSES_TO_INTEGER
has explicit null handling such that a null actual
string corresponds to a null expected integer; CONTAINS_SUBSTRING
does not,
and any test which sees a null string will fail.
Iterable Example
Iterable<String> actual = ImmutableList.of("+64", "+128", "+256", "0x80");
assertThat(actual)
.comparingElementsUsing(STRING_PARSES_TO_INTEGER)
.containsExactly(64, 128, 256, 128)
.inOrder();
Map Example
N.B. The Correspondence
applies to the values, not the keys, of the map. You
should almost always make assertions using equality of map keys, since equality
semantics are used for lookups.
Map<String, String> actual = ImmutableMap.of("abc", "123", "def", "456");
assertThat(actual)
.comparingValuesUsing(STRING_PARSES_TO_INTEGER)
.containsExactly("def", 456, "abc", 123);
Multimap Example
N.B. The Correspondence
applies to the values, not the keys, of the multimap.
You should almost always make assertions using equality of multimap keys, since
equality semantics are used for lookups.
Multimap<String, String> actual =
ImmutableListMultimap.of("abc", "123", "def", "456", "def", "789");
assertThat(actual)
.comparingValuesUsing(STRING_PARSES_TO_INTEGER)
.containsEntry("def", 789);
Getting better failure messages
When an assertion involving collections of objects with verbose toString()
representations (such as value types) fails, the failure messages can often be
hard to understand. There are a couple of things you can do to make debugging
failing tests easier.
Enabling pairing of Iterable
elements
If you are making an assertion about an Iterable
, and you know of some key
function which uniquely indexes the expected elements, then you can use the
displayingDiffsPairedBy
method to tell Fuzzy
Truth about it. For example, if you have a type called MyRecord
, and you’re
making an assertion about an Iterable<MyRecord>
using a Correspondence
called RECORD_EQUIVALENCE
, and the expected records have unique IDs returned
by a getId()
method, then you could write this:
assertThat(actualRecords)
.comparingElementsUsing(RECORD_EQUIVALENCE)
.displayingDiffsPairedBy(MyRecord::getId)
.containsExactlyElementsIn(expectedRecords);
If this assertion fails, the failure message will pair up any missing and
unexpected elements by their IDs. For example, it might tell you that the actual
Iterable
was missing an element with ID 2, that it had an unexpected element
with ID 3, or that the element with ID 4 wasn’t equivalent to the one it
expected.
(If an assertion about a Map
fails, the failure message will automatically
match up any missing and unexpected entries using their keys. You can think of
the displayingDiffsPairedBy
method as providing an equivalent for an assertion
about an Iterable
. Note that this won’t affect whether the test passes or
fails.)
Enabling formatted diffs between elements
Your Correspondence
instance may optionally provide functionality which takes
an actual and an expected element and returns a String
describing how they
differ. For example, a Correspondence
that describes whether two instances of
a value type are equivalent might provide diff-formatting to describe which
properties of the value types are different. You can supply this functionality
using the formattingDiffsUsing
.
Here’s an example correspondence between MyRecord
instances:
class RecordTestHelper {
static final Correspondence<MyRecord, MyRecord> RECORD_EQUIVALENCE =
Correspondence.from(MyRecordTestHelper::recordsEquivalent, "is equivalent to")
.formattingDiffsUsing(MyRecordTestHelper::formatRecordDiff);
static boolean recordsEquivalent(@Nullable MyRecord actual, @Nullable MyRecord expected) {
// code to check whether records should be considered equivalent for testing purposes
}
static String formatRecordDiff(@Nullable MyRecord actual, @Nullable MyRecord expected) {
// code to format the diff between the records
}
}
When you do this, Fuzzy Truth will include these formatted diffs in failure
messages whenever it can usefully do so. For example, when an assertion about a
Map
fails, and there is an entry which has the right key but the wrong value,
it will show a diff between the value it got and the one it expected. For this
to work for an assertion about an Iterable
(with more than one missing value)
you need to enable pairing, as shown above.
Enabling formatted diffs while still using object equality for comparisons
You can get the same diff-formatting functionality while using object equality
for comparisons (i.e. making the same assertion as you would get with non-Fuzzy
Truth) by using a method such as
formattingDiffsUsing
. For example:
assertThat(actualRecords)
.formattingDiffsUsing(MyRecordTestHelper::formatRecordDiff)
.displayingDiffsPairedBy(MyRecord::getId)
.containsExactlyElementsIn(expectedRecords);
Protocol buffers
If you want to assert about a proto, an Iterable
of protos, or a Map
or
Multimap
with proto values, you should normally use Proto Truth.
It is built on top of Fuzzy Truth and allows you to express most assertions more
concisely.