Skip to content

Commit c60c71a

Browse files
petegronshapiro
authored andcommitted
Add the one-function transforming factory method for Correspondence.
RELNOTES=You can now create a Correspondence instance that transforms the actual elements using a lambda or method reference and tests for equality with the expected elements, with the Correspondence.transforming factory method. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=233729880
1 parent dff559d commit c60c71a

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed

core/src/main/java/com/google/common/truth/Correspondence.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.google.common.truth;
1717

18+
import static com.google.common.base.Functions.identity;
1819
import static com.google.common.base.Preconditions.checkNotNull;
1920
import static com.google.common.base.Preconditions.checkState;
2021
import static com.google.common.truth.DoubleSubject.checkTolerance;
@@ -24,7 +25,9 @@
2425
import static com.google.common.truth.Platform.getStackTraceAsString;
2526
import static java.util.Arrays.asList;
2627

28+
import com.google.common.base.Function;
2729
import com.google.common.base.Joiner;
30+
import com.google.common.base.Objects;
2831
import com.google.common.base.Strings;
2932
import com.google.common.collect.ImmutableList;
3033
import java.util.Arrays;
@@ -56,6 +59,8 @@
5659
*
5760
* @author Pete Gillin
5861
*/
62+
// TODO(b/119038898): Once there is little reason to prefer subclassing to factory methods, change
63+
// the class-level javadoc to point folks at the factory methods and drop references to subclassing.
5964
public abstract class Correspondence<A, E> {
6065

6166
/**
@@ -146,6 +151,75 @@ public String toString() {
146151
}
147152
}
148153

154+
/**
155+
* Constructs a {@link Correspondence} that compares elements by transforming the actual elements
156+
* using the given function and tests for equality with the expected elements. If the transformed
157+
* actual element (i.e. the output of the given function) is null, it will correspond to a null
158+
* expected element.
159+
*
160+
* <p>The correspondence does not support formatting of diffs (see {@link #formatDiff}).
161+
*
162+
* <p>Note that, if you the data you are asserting about contains null actual values, your
163+
* function may be invoked with a null argument. If this causes it to throw a {@link
164+
* NullPointerException}, then your test will fail. (See {@link Correspondence#compare} for more
165+
* detail on how exceptions are handled.) In particular, this applies if your predicate is an
166+
* instance method reference on the actual value (as in the example below). If you want a null
167+
* actual element to correspond to a null expected element, you must ensure that your function
168+
* transforms a null input to a null output.
169+
*
170+
* <p>Example:
171+
*
172+
* <pre>{@code
173+
* static final Correspondence<MyRecord, Integer> HAS_ID =
174+
* Correspondence.transforming(MyRecord::getId, "has an ID of");
175+
* }</pre>
176+
*
177+
* This can be used as follows:
178+
*
179+
* <pre>{@code
180+
* assertThat(myRecords).comparingElementsUsing(HAS_ID).containsExactly(123, 456, 789);
181+
* }</pre>
182+
*
183+
* @param actualTransform a {@link Function} taking an actual value and returning a new value
184+
* which will be compared with an expected value to determine whether they correspond
185+
* @param description should fill the gap in a failure message of the form {@code "not true that
186+
* <some actual element> is an element that <description> <some expected element>"}, e.g.
187+
* {@code "has an ID of"}
188+
*/
189+
// TODO(b/119038898): Mention formattingDiffsUsing in the javadoc when it exists.
190+
public static <A, E> Correspondence<A, E> transforming(
191+
Function<A, ? extends E> actualTransform, String description) {
192+
return new Transforming<>(actualTransform, identity(), description);
193+
}
194+
195+
// TODO(b/119038894): Add the two-function overload of transforming that also transforms expected.
196+
197+
private static final class Transforming<A, E> extends Correspondence<A, E> {
198+
199+
private final Function<? super A, ?> actualTransform;
200+
private final Function<? super E, ?> expectedTransform;
201+
private final String description;
202+
203+
private Transforming(
204+
Function<? super A, ?> actualTransform,
205+
Function<? super E, ?> expectedTransform,
206+
String description) {
207+
this.actualTransform = actualTransform;
208+
this.expectedTransform = expectedTransform;
209+
this.description = description;
210+
}
211+
212+
@Override
213+
public boolean compare(@NullableDecl A actual, @NullableDecl E expected) {
214+
return Objects.equal(actualTransform.apply(actual), expectedTransform.apply(expected));
215+
}
216+
217+
@Override
218+
public String toString() {
219+
return description;
220+
}
221+
}
222+
149223
/**
150224
* Returns a {@link Correspondence} between {@link Number} instances that considers instances to
151225
* correspond (i.e. {@link Correspondence#compare(Object, Object)} returns {@code true}) if the

core/src/test/java/com/google/common/truth/CorrespondenceTest.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import static java.util.Arrays.asList;
2121
import static org.junit.Assert.fail;
2222

23+
import com.google.common.base.Function;
2324
import com.google.common.collect.ImmutableList;
25+
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
2426
import org.junit.Test;
2527
import org.junit.runner.RunWith;
2628
import org.junit.runners.JUnit4;
@@ -135,6 +137,97 @@ public void testFrom_viaIterableSubjectContainsExactly_null() {
135137
.startsWith("compare(null, foot) threw java.lang.NullPointerException");
136138
}
137139

140+
// Tests of the 'transform' factory methods.
141+
142+
private static final Correspondence<String, Integer> LENGTHS =
143+
Correspondence.transforming(
144+
new Function<String, Integer>() {
145+
@Override
146+
@NullableDecl
147+
public Integer apply(String str) {
148+
return str.length();
149+
}
150+
},
151+
"has a length of");
152+
153+
private static final Correspondence<String, Integer> HYPHEN_INDEXES =
154+
Correspondence.transforming(
155+
new Function<String, Integer>() {
156+
@Override
157+
@NullableDecl
158+
public Integer apply(String str) {
159+
int index = str.indexOf('-');
160+
return (index >= 0) ? index : null;
161+
}
162+
},
163+
"has a hyphen at an index of");
164+
165+
@Test
166+
public void testTransforming_actual_compare() {
167+
assertThat(LENGTHS.compare("foo", 3)).isTrue();
168+
assertThat(LENGTHS.compare("foot", 4)).isTrue();
169+
assertThat(LENGTHS.compare("foo", 4)).isFalse();
170+
}
171+
172+
@Test
173+
public void testTransforming_actual_formatDiff() {
174+
assertThat(LENGTHS.formatDiff("foo", 4)).isNull();
175+
}
176+
177+
@Test
178+
public void testTransforming_actual_toString() {
179+
assertThat(LENGTHS.toString()).isEqualTo("has a length of");
180+
}
181+
182+
@Test
183+
public void testTransforming_actual_viaIterableSubjectContainsExactly_success() {
184+
assertThat(ImmutableList.of("feet", "barns", "gallons"))
185+
.comparingElementsUsing(LENGTHS)
186+
.containsExactly(4, 5, 7)
187+
.inOrder();
188+
}
189+
190+
@Test
191+
public void testTransforming_actual_viaIterableSubjectContainsExactly_failure() {
192+
expectFailure
193+
.whenTesting()
194+
.that(ImmutableList.of("feet", "barns", "gallons"))
195+
.comparingElementsUsing(LENGTHS)
196+
.containsExactly(4, 5);
197+
assertThat(expectFailure.getFailure())
198+
.hasMessageThat()
199+
.isEqualTo(
200+
"Not true that <[feet, barns, gallons]> contains exactly one element that has a length "
201+
+ "of each element of <[4, 5]>. It has unexpected elements <[gallons]>");
202+
}
203+
204+
@Test
205+
public void testTransforming_actual_viaIterableSubjectContainsExactly_nullActual() {
206+
expectFailure
207+
.whenTesting()
208+
.that(asList("feet", "barns", null))
209+
.comparingElementsUsing(LENGTHS)
210+
.containsExactly(4, 5);
211+
assertFailureKeys(
212+
"Not true that <[feet, barns, null]> contains exactly one element that has a length of "
213+
+ "each element of <[4, 5]>. It has unexpected elements <[null]>",
214+
"additionally, one or more exceptions were thrown while comparing elements",
215+
"first exception");
216+
assertThatFailure()
217+
.factValue("first exception")
218+
.startsWith("compare(null, 4) threw java.lang.NullPointerException");
219+
}
220+
221+
@Test
222+
public void testTransforming_actual_viaIterableSubjectContainsExactly_nullTransformed() {
223+
// "mailing-list" and "chat-room" have hyphens at index 7 and 4 respectively.
224+
// "forum" contains no hyphen so the Function in HYPHEN_INDEXES transforms it to null.
225+
assertThat(ImmutableList.of("mailing-list", "chat-room", "forum"))
226+
.comparingElementsUsing(HYPHEN_INDEXES)
227+
.containsExactly(7, 4, null)
228+
.inOrder();
229+
}
230+
138231
// Tests of the 'tolerance' factory method. Includes both direct tests of the compare method and
139232
// indirect tests using it in a basic call chain.
140233

0 commit comments

Comments
 (0)