com.google.common.truth.Correspondence Maven / Gradle / Ivy
/*
* Copyright (c) 2016 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.truth;
import static com.google.common.base.Functions.identity;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.DoubleSubject.checkTolerance;
import static com.google.common.truth.Fact.fact;
import static com.google.common.truth.Fact.simpleFact;
import static com.google.common.truth.Platform.getStackTraceAsString;
import static com.google.common.truth.SubjectUtils.asList;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Determines whether an instance of type {@code A} corresponds in some way to an instance of type
* {@code E} for the purposes of a test assertion. For example, the implementation returned by the
* {@link #tolerance(double)} factory method implements approximate equality between numeric values,
* with values being said to correspond if the difference between them does not exceed the given
* fixed tolerance. The instances of type {@code A} are typically actual values from a collection
* returned by the code under test; the instances of type {@code E} are typically expected values
* with which the actual values are compared by the test.
*
* The correspondence is required to be consistent: for any given values {@code actual} and
* {@code expected}, multiple invocations of {@code compare(actual, expected)} must consistently
* return {@code true} or consistently return {@code false} (provided that neither value is
* modified). Although {@code A} and {@code E} will often be the same types, they are not
* required to be the same, and even if they are it is not required that the correspondence
* should have any of the other properties of an equivalence relation (reflexivity, symmetry, or
* transitivity).
*
*
Optionally, instances of this class can also provide functionality to format the difference
* between values which do not correspond. This results in failure messages including formatted
* diffs between expected and actual value, where possible.
*
*
The recommended approach for creating an instance of this class is to use one of the static
* factory methods. The most general of these is {@link #from}; the other methods are more
* convenient in specific cases. The optional diff-formatting functionality can be added using
* {@link #formattingDiffsUsing}. (Alternatively, you can subclass this class yourself, but that is
* generally not recommended.)
*
*
Instances of this are typically used via {@link IterableSubject#comparingElementsUsing},
* {@link MapSubject#comparingValuesUsing}, or {@link MultimapSubject#comparingValuesUsing}.
*
* @author Pete Gillin
*/
public abstract class Correspondence {
/**
* Constructs a {@link Correspondence} that compares actual and expected elements using the given
* binary predicate.
*
* The correspondence does not support formatting of diffs (see {@link #formatDiff}). You can
* add that behaviour by calling {@link Correspondence#formattingDiffsUsing}.
*
*
Note that, if the data you are asserting about contains nulls, your predicate may be invoked
* with null arguments. If this causes it to throw a {@link NullPointerException}, then your test
* will fail. (See {@link Correspondence#compare} for more detail on how exceptions are handled.)
* In particular, if your predicate is an instance method reference on the actual value (as in the
* {@code String::contains} example below), your test will fail if it sees null actual values.
*
*
Example using an instance method reference:
*
*
{@code
* static final Correspondence CONTAINS_SUBSTRING =
* Correspondence.from(String::contains, "contains");
* }
*
* Example using a static method reference:
*
*
{@code
* class MyRecordTestHelper {
* static final Correspondence EQUIVALENCE =
* Correspondence.from(MyRecordTestHelper::recordsEquivalent, "is equivalent to");
* static boolean recordsEquivalent(MyRecord actual, MyRecord expected) {
* // code to check whether records should be considered equivalent for testing purposes
* }
* }
* }
*
* Example using a lambda:
*
*
{@code
* static final Correspondence
*
* @param predicate a {@link BinaryPredicate} taking an actual and expected value (in that order)
* and returning whether the actual value corresponds to the expected value in some way
* @param description should fill the gap in a failure message of the form {@code "not true that
* is an element that "}, e.g.
* {@code "contains"}, {@code "is an instance of"}, or {@code "is equivalent to"}
*/
public static Correspondence from(
BinaryPredicate predicate, String description) {
return new FromBinaryPredicate<>(predicate, description);
}
/**
* A functional interface for a binary predicate, to be used to test whether a pair of objects of
* types {@code A} and {@code E} satisfy some condition.
*
* This interface will normally be implemented using a lambda or a method reference, and the
* resulting object will normally be passed directly to {@link Correspondence#from}. As a result,
* you should almost never see {@code BinaryPredicate} used as the type of a field or variable, or
* a return type.
*/
public interface BinaryPredicate {
/**
* Returns whether or not the actual and expected values satisfy the condition defined by this
* predicate.
*/
boolean apply(A actual, E expected);
}
private static final class FromBinaryPredicate<
A extends @Nullable Object, E extends @Nullable Object>
extends Correspondence {
private final BinaryPredicate predicate;
private final String description;
private FromBinaryPredicate(BinaryPredicate correspondencePredicate, String description) {
this.predicate = checkNotNull(correspondencePredicate);
this.description = checkNotNull(description);
}
@Override
public boolean compare(A actual, E expected) {
return predicate.apply(actual, expected);
}
@Override
public String toString() {
return description;
}
}
/**
* Constructs a {@link Correspondence} that compares elements by transforming the actual elements
* using the given function and testing for equality with the expected elements. If the
* transformed actual element (i.e. the output of the given function) is null, it will correspond
* to a null expected element.
*
* The correspondence does not support formatting of diffs (see {@link #formatDiff}). You can
* add that behaviour by calling {@link Correspondence#formattingDiffsUsing}.
*
*
Note that, if you the data you are asserting about contains null actual values, your
* function may be invoked with a null argument. If this causes it to throw a {@link
* NullPointerException}, then your test will fail. (See {@link Correspondence#compare} for more
* detail on how exceptions are handled.) In particular, this applies if your function is an
* instance method reference on the actual value (as in the example below). If you want a null
* actual element to correspond to a null expected element, you must ensure that your function
* transforms a null input to a null output.
*
*
Example:
*
*
{@code
* static final Correspondence HAS_ID =
* Correspondence.transforming(MyRecord::getId, "has an ID of");
* }
*
* This can be used as follows:
*
* {@code
* assertThat(myRecords).comparingElementsUsing(HAS_ID).containsExactly(123, 456, 789);
* }
*
* @param actualTransform a {@link Function} taking an actual value and returning a new value
* which will be compared with an expected value to determine whether they correspond
* @param description should fill the gap in a failure message of the form {@code "not true that
* is an element that "}, e.g.
* {@code "has an ID of"}
*/
public static
Correspondence transforming(
Function actualTransform, String description) {
return new Transforming<>(actualTransform, identity(), description);
}
/**
* Constructs a {@link Correspondence} that compares elements by transforming the actual and the
* expected elements using the given functions and testing the transformed values for equality. If
* an actual element is transformed to null, it will correspond to an expected element that is
* also transformed to null.
*
* The correspondence does not support formatting of diffs (see {@link #formatDiff}). You can
* add that behaviour by calling {@link Correspondence#formattingDiffsUsing}.
*
*
Note that, if you the data you are asserting about contains null actual or expected values,
* the appropriate function may be invoked with a null argument. If this causes it to throw a
* {@link NullPointerException}, then your test will fail. (See {@link Correspondence#compare} for
* more detail on how exceptions are handled.) In particular, this applies if your function is an
* instance method reference on the actual or expected value (as in the example below). If you
* want a null actual element to correspond to a null expected element, you must ensure that your
* functions both transform a null input to a null output.
*
*
If you want to apply the same function to both the actual and expected elements, just
* provide the same argument twice.
*
*
Example:
*
*
{@code
* static final Correspondence SAME_IDS =
* Correspondence.transforming(MyRequest::getId, MyResponse::getId, "has the same ID as");
* }
*
* This can be used as follows:
*
* {@code
* assertThat(myResponses).comparingElementsUsing(SAME_IDS).containsExactlyElementsIn(myRequests);
* }
*
* @param actualTransform a {@link Function} taking an actual value and returning a new value
* which will be compared with a transformed expected value to determine whether they
* correspond
* @param expectedTransform a {@link Function} taking an expected value and returning a new value
* which will be compared with a transformed actual value
* @param description should fill the gap in a failure message of the form {@code "not true that
* is an element that "}, e.g.
* {@code "has the same ID as"}
*/
public static
Correspondence transforming(
Function actualTransform, Function expectedTransform, String description) {
return new Transforming<>(actualTransform, expectedTransform, description);
}
private static final class Transforming
extends Correspondence {
private final Function super A, ?> actualTransform;
private final Function super E, ?> expectedTransform;
private final String description;
private Transforming(
Function super A, ?> actualTransform,
Function super E, ?> expectedTransform,
String description) {
this.actualTransform = actualTransform;
this.expectedTransform = expectedTransform;
this.description = description;
}
@Override
public boolean compare(A actual, E expected) {
return Objects.equal(actualTransform.apply(actual), expectedTransform.apply(expected));
}
@Override
public String toString() {
return description;
}
}
/**
* Returns a {@link Correspondence} between {@link Number} instances that considers instances to
* correspond (i.e. {@link Correspondence#compare(Object, Object)} returns {@code true}) if the
* double values of each instance (i.e. the result of calling {@link Number#doubleValue()} on
* them) are finite values within {@code tolerance} of each other.
*
*
* - It does not consider instances to correspond if either value is infinite or NaN.
*
- The conversion to double may result in a loss of precision for some numeric types.
*
- The {@link Correspondence#compare(Object, Object)} method throws a {@link
* NullPointerException} if either {@link Number} instance is null.
*
*
* @param tolerance an inclusive upper bound on the difference between the double values of the
* two {@link Number} instances, which must be a non-negative finite value, i.e. not {@link
* Double#NaN}, {@link Double#POSITIVE_INFINITY}, or negative, including {@code -0.0}
*/
public static Correspondence tolerance(double tolerance) {
return new TolerantNumericEquality(tolerance);
}
private static final class TolerantNumericEquality extends Correspondence {
private final double tolerance;
private TolerantNumericEquality(double tolerance) {
checkTolerance(tolerance);
this.tolerance = tolerance;
}
@Override
public boolean compare(Number actual, Number expected) {
double actualDouble = checkNotNull(actual).doubleValue();
double expectedDouble = checkNotNull(expected).doubleValue();
return MathUtil.equalWithinTolerance(actualDouble, expectedDouble, tolerance);
}
@Override
public String toString() {
return "is a finite number within " + tolerance + " of";
}
}
/**
* Returns a correspondence which compares elements using object equality, i.e. giving the same
* assertions as you would get without a correspondence. This exists so that we can add a
* diff-formatting functionality to it. See e.g. {@link IterableSubject#formattingDiffsUsing}.
*/
@SuppressWarnings("unchecked") // safe covariant cast
static Correspondence equality() {
return (Equality) Equality.INSTANCE;
}
private static final class Equality extends Correspondence {
private static final Equality