Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ these methods will need to be updated. The following methods are affected:

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


## Version 3.0 (1st August 2024)

Expand Down
8 changes: 5 additions & 3 deletions hamcrest/src/main/java/org/hamcrest/Matchers.java
Original file line number Diff line number Diff line change
Expand Up @@ -1662,18 +1662,20 @@ public static <T> org.hamcrest.Matcher<T> oneOf(T... elements) {
}

/**
* Creates a matcher of {@link Double}s that matches when an examined double is equal
* Creates a matcher of {@link Number}s that matches when an examined number is equal
* to the specified <code>operand</code>, within a range of +/- <code>error</code>.
* Comparison is performed using {@link Number#doubleValue()}.
* For example:
* <pre>assertThat(1.03, is(closeTo(1.0, 0.03)))</pre>
* <pre>assertThat(1.0f, is(closeTo(0.99, 0.1)))</pre>
*
* @param operand
* the expected value of matching doubles
* the expected value of matching numbers
* @param error
* the delta (+/-) within which matches will be allowed
* @return The matcher.
*/
public static org.hamcrest.Matcher<java.lang.Double> closeTo(double operand, double error) {
public static org.hamcrest.Matcher<java.lang.Number> closeTo(double operand, double error) {
return org.hamcrest.number.IsCloseTo.closeTo(operand, error);
}

Expand Down
18 changes: 10 additions & 8 deletions hamcrest/src/main/java/org/hamcrest/number/IsCloseTo.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
/**
* Is the value a number equal to a value within some range of acceptable error?
*/
public class IsCloseTo extends TypeSafeMatcher<Double> {
public class IsCloseTo extends TypeSafeMatcher<Number> {

private final double delta;
private final double value;
Expand All @@ -25,12 +25,12 @@ public IsCloseTo(double value, double error) {
}

@Override
public boolean matchesSafely(Double item) {
public boolean matchesSafely(Number item) {
return actualDelta(item) <= 0.0;
}

@Override
public void describeMismatchSafely(Double item, Description mismatchDescription) {
public void describeMismatchSafely(Number item, Description mismatchDescription) {
mismatchDescription.appendValue(item)
.appendText(" differed by ")
.appendValue(actualDelta(item))
Expand All @@ -46,23 +46,25 @@ public void describeTo(Description description) {
.appendValue(value);
}

private double actualDelta(Double item) {
return abs(item - value) - delta;
private double actualDelta(Number item) {
return abs(item.doubleValue() - value) - delta;
}

/**
* Creates a matcher of {@link Double}s that matches when an examined double is equal
* Creates a matcher of {@link Number}s that matches when an examined number is equal
* to the specified <code>operand</code>, within a range of +/- <code>error</code>.
* Comparison is performed using {@link Number#doubleValue()}.
* For example:
* <pre>assertThat(1.03, is(closeTo(1.0, 0.03)))</pre>
* <pre>assertThat(1.0f, is(closeTo(0.99, 0.1)))</pre>
*
* @param operand
* the expected value of matching doubles
* the expected value of matching numbers
* @param error
* the delta (+/-) within which matches will be allowed
* @return The matcher.
*/
public static Matcher<Double> closeTo(double operand, double error) {
public static Matcher<Number> closeTo(double operand, double error) {
return new IsCloseTo(operand, error);
}

Expand Down
65 changes: 64 additions & 1 deletion hamcrest/src/test/java/org/hamcrest/number/IsCloseToTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
import org.hamcrest.Matcher;
import org.junit.jupiter.api.Test;

import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.test.MatcherAssertions.*;
import static org.hamcrest.number.IsCloseTo.closeTo;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class IsCloseToTest extends AbstractMatcherTest {

private final Matcher<Double> matcher = closeTo(1.0d, 0.5d);
private final Matcher<Number> matcher = closeTo(1.0d, 0.5d);

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

@Test
public void test_matchesIfArgumentIsEqualToAFloatValueWithinSomeError() {
assertMatches("1.0f", matcher, 1.0f);
assertMatches("0.5f", matcher, 0.5f);
assertMatches("1.5f", matcher, 1.5f);

assertDoesNotMatch("too large", matcher, 2.0f);
assertMismatchDescription("<3.0F> differed by <1.5> more than delta <0.5>", matcher, 3.0f);
assertDoesNotMatch("number too small", matcher, 0.0f);
assertMismatchDescription("<0.0F> differed by <0.5> more than delta <0.5>", matcher, 0.0f);
}

@Test
public void test_matchesIfArgumentIsEqualToAnIntegerValueWithinSomeError() {
assertMatches("1", matcher, 1);
assertDoesNotMatch("too large", matcher, 2);
assertMismatchDescription("<3> differed by <1.5> more than delta <0.5>", matcher, 3);
assertDoesNotMatch("number too small", matcher, 0);
assertMismatchDescription("<0> differed by <0.5> more than delta <0.5>", matcher, 0);
}

@Test
public void test_matchesIfArgumentIsEqualToALongValueWithinSomeError() {
assertMatches("1L", matcher, 1L);
assertDoesNotMatch("too large", matcher, 2L);
assertMismatchDescription("<3L> differed by <1.5> more than delta <0.5>", matcher, 3L);
assertDoesNotMatch("number too small", matcher, 0L);
assertMismatchDescription("<0L> differed by <0.5> more than delta <0.5>", matcher, 0L);
}

/**
* Regression for #444: float bean properties failed when closeTo was used with
* hasProperty / is chaining because TypeSafeMatcher only accepted Double.
*/
@Test
public void test_matchesFloatPropertyWhenChainedWithHasProperty() {
final BeanWithFloatNumber subject = new BeanWithFloatNumber(1.0f);

assertMatches(hasProperty("number", closeTo(0.99, 0.1)), subject);
assertMatches(hasProperty("number", is(closeTo(0.99, 0.1))), subject);
assertDoesNotMatch(hasProperty("number", closeTo(0.5, 0.1)), subject);
// Must report numeric distance, not "was a java.lang.Float" type rejection (#444).
final String mismatch = mismatchDescription(hasProperty("number", closeTo(0.5, 0.1)), subject);
assertTrue(mismatch.contains("differed by"), "unexpected mismatch: " + mismatch);
assertTrue(!mismatch.contains("was a java.lang.Float"), "unexpected type rejection: " + mismatch);
}

@Test
public void test_is_self_describing() {
assertDescription("a numeric value within <0.5> of <1.0>", matcher);
}

public static final class BeanWithFloatNumber {
private final float number;

public BeanWithFloatNumber(float number) {
this.number = number;
}

public float getNumber() {
return number;
}
}

}