Skip to content

Commit b3d75b3

Browse files
committed
fix: allow closeTo to match any Number (Float, Integer, etc.)
TypeSafeMatcher<Double> rejected boxed Float/Integer values before comparing, so hasProperty("x", closeTo(...)) failed for float bean properties even when the numeric value was within delta. Fixes #444
1 parent 3d58e99 commit b3d75b3

4 files changed

Lines changed: 82 additions & 12 deletions

File tree

‎CHANGES.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ these methods will need to be updated. The following methods are affected:
3535

3636
* Allow matching against polymorphic collections ([#252](https://github.com/hamcrest/JavaHamcrest/issues/252),
3737
[PR #422](https://github.com/hamcrest/JavaHamcrest/pull/422))
38+
* `closeTo` accepts any `Number` (e.g. Float properties with `hasProperty`/`is` chaining),
39+
not only Double ([#444](https://github.com/hamcrest/JavaHamcrest/issues/444))
40+
3841

3942
## Version 3.0 (1st August 2024)
4043

‎hamcrest/src/main/java/org/hamcrest/Matchers.java‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1662,18 +1662,20 @@ public static <T> org.hamcrest.Matcher<T> oneOf(T... elements) {
16621662
}
16631663

16641664
/**
1665-
* Creates a matcher of {@link Double}s that matches when an examined double is equal
1665+
* Creates a matcher of {@link Number}s that matches when an examined number is equal
16661666
* to the specified <code>operand</code>, within a range of +/- <code>error</code>.
1667+
* Comparison is performed using {@link Number#doubleValue()}.
16671668
* For example:
16681669
* <pre>assertThat(1.03, is(closeTo(1.0, 0.03)))</pre>
1670+
* <pre>assertThat(1.0f, is(closeTo(0.99, 0.1)))</pre>
16691671
*
16701672
* @param operand
1671-
* the expected value of matching doubles
1673+
* the expected value of matching numbers
16721674
* @param error
16731675
* the delta (+/-) within which matches will be allowed
16741676
* @return The matcher.
16751677
*/
1676-
public static org.hamcrest.Matcher<java.lang.Double> closeTo(double operand, double error) {
1678+
public static org.hamcrest.Matcher<java.lang.Number> closeTo(double operand, double error) {
16771679
return org.hamcrest.number.IsCloseTo.closeTo(operand, error);
16781680
}
16791681

‎hamcrest/src/main/java/org/hamcrest/number/IsCloseTo.java‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
/**
1010
* Is the value a number equal to a value within some range of acceptable error?
1111
*/
12-
public class IsCloseTo extends TypeSafeMatcher<Double> {
12+
public class IsCloseTo extends TypeSafeMatcher<Number> {
1313

1414
private final double delta;
1515
private final double value;
@@ -25,12 +25,12 @@ public IsCloseTo(double value, double error) {
2525
}
2626

2727
@Override
28-
public boolean matchesSafely(Double item) {
28+
public boolean matchesSafely(Number item) {
2929
return actualDelta(item) <= 0.0;
3030
}
3131

3232
@Override
33-
public void describeMismatchSafely(Double item, Description mismatchDescription) {
33+
public void describeMismatchSafely(Number item, Description mismatchDescription) {
3434
mismatchDescription.appendValue(item)
3535
.appendText(" differed by ")
3636
.appendValue(actualDelta(item))
@@ -46,23 +46,25 @@ public void describeTo(Description description) {
4646
.appendValue(value);
4747
}
4848

49-
private double actualDelta(Double item) {
50-
return abs(item - value) - delta;
49+
private double actualDelta(Number item) {
50+
return abs(item.doubleValue() - value) - delta;
5151
}
5252

5353
/**
54-
* Creates a matcher of {@link Double}s that matches when an examined double is equal
54+
* Creates a matcher of {@link Number}s that matches when an examined number is equal
5555
* to the specified <code>operand</code>, within a range of +/- <code>error</code>.
56+
* Comparison is performed using {@link Number#doubleValue()}.
5657
* For example:
5758
* <pre>assertThat(1.03, is(closeTo(1.0, 0.03)))</pre>
59+
* <pre>assertThat(1.0f, is(closeTo(0.99, 0.1)))</pre>
5860
*
5961
* @param operand
60-
* the expected value of matching doubles
62+
* the expected value of matching numbers
6163
* @param error
6264
* the delta (+/-) within which matches will be allowed
6365
* @return The matcher.
6466
*/
65-
public static Matcher<Double> closeTo(double operand, double error) {
67+
public static Matcher<Number> closeTo(double operand, double error) {
6668
return new IsCloseTo(operand, error);
6769
}
6870

‎hamcrest/src/test/java/org/hamcrest/number/IsCloseToTest.java‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
import org.hamcrest.Matcher;
55
import org.junit.jupiter.api.Test;
66

7+
import static org.hamcrest.Matchers.hasProperty;
8+
import static org.hamcrest.Matchers.is;
79
import static org.hamcrest.test.MatcherAssertions.*;
810
import static org.hamcrest.number.IsCloseTo.closeTo;
11+
import static org.junit.jupiter.api.Assertions.assertTrue;
912

1013
public class IsCloseToTest extends AbstractMatcherTest {
1114

12-
private final Matcher<Double> matcher = closeTo(1.0d, 0.5d);
15+
private final Matcher<Number> matcher = closeTo(1.0d, 0.5d);
1316

1417
@Override
1518
protected Matcher<?> createMatcher() {
@@ -29,9 +32,69 @@ public void test_matchesIfArgumentIsEqualToADoubleValueWithinSomeError() {
2932
assertMismatchDescription("<0.1> differed by <0.4> more than delta <0.5>", matcher, 0.1);
3033
}
3134

35+
@Test
36+
public void test_matchesIfArgumentIsEqualToAFloatValueWithinSomeError() {
37+
assertMatches("1.0f", matcher, 1.0f);
38+
assertMatches("0.5f", matcher, 0.5f);
39+
assertMatches("1.5f", matcher, 1.5f);
40+
41+
assertDoesNotMatch("too large", matcher, 2.0f);
42+
assertMismatchDescription("<3.0F> differed by <1.5> more than delta <0.5>", matcher, 3.0f);
43+
assertDoesNotMatch("number too small", matcher, 0.0f);
44+
assertMismatchDescription("<0.0F> differed by <0.5> more than delta <0.5>", matcher, 0.0f);
45+
}
46+
47+
@Test
48+
public void test_matchesIfArgumentIsEqualToAnIntegerValueWithinSomeError() {
49+
assertMatches("1", matcher, 1);
50+
assertDoesNotMatch("too large", matcher, 2);
51+
assertMismatchDescription("<3> differed by <1.5> more than delta <0.5>", matcher, 3);
52+
assertDoesNotMatch("number too small", matcher, 0);
53+
assertMismatchDescription("<0> differed by <0.5> more than delta <0.5>", matcher, 0);
54+
}
55+
56+
@Test
57+
public void test_matchesIfArgumentIsEqualToALongValueWithinSomeError() {
58+
assertMatches("1L", matcher, 1L);
59+
assertDoesNotMatch("too large", matcher, 2L);
60+
assertMismatchDescription("<3L> differed by <1.5> more than delta <0.5>", matcher, 3L);
61+
assertDoesNotMatch("number too small", matcher, 0L);
62+
assertMismatchDescription("<0L> differed by <0.5> more than delta <0.5>", matcher, 0L);
63+
}
64+
65+
/**
66+
* Regression for #444: float bean properties failed when closeTo was used with
67+
* hasProperty / is chaining because TypeSafeMatcher only accepted Double.
68+
*/
69+
@Test
70+
public void test_matchesFloatPropertyWhenChainedWithHasProperty() {
71+
final BeanWithFloatNumber subject = new BeanWithFloatNumber(1.0f);
72+
73+
assertMatches(hasProperty("number", closeTo(0.99, 0.1)), subject);
74+
assertMatches(hasProperty("number", is(closeTo(0.99, 0.1))), subject);
75+
assertDoesNotMatch(hasProperty("number", closeTo(0.5, 0.1)), subject);
76+
// Must report numeric distance, not "was a java.lang.Float" type rejection (#444).
77+
final String mismatch = mismatchDescription(hasProperty("number", closeTo(0.5, 0.1)), subject);
78+
assertTrue(mismatch.contains("differed by"), "unexpected mismatch: " + mismatch);
79+
assertTrue(!mismatch.contains("was a java.lang.Float"), "unexpected type rejection: " + mismatch);
80+
}
81+
3282
@Test
3383
public void test_is_self_describing() {
3484
assertDescription("a numeric value within <0.5> of <1.0>", matcher);
3585
}
3686

87+
public static final class BeanWithFloatNumber {
88+
private final float number;
89+
90+
public BeanWithFloatNumber(float number) {
91+
this.number = number;
92+
}
93+
94+
public float getNumber() {
95+
return number;
96+
}
97+
}
98+
3799
}
100+

0 commit comments

Comments
 (0)