Search code examples
javaunit-testinghamcrest

hamcrest TypeSafeMatcher fails matching of double array


I wrote this matcher to check the contents of a double[]:

@SuppressWarnings({ "rawtypes", "unchecked" })
public static Matcher<double[]> isArrayCloseTo(double[] expected) {
    final double DELTA = 1e-10;
    List<Matcher<Double>> matchers = new ArrayList<>();
    for (double d : expected)
        matchers.add(new IsCloseTo(d, DELTA));
    return new IsArray(matchers.toArray(new Matcher[matchers.size()]));
}

I suppress those warnings because there's nothing I can do about an array not having a generic type. The method looks fine, but it always fails:

assertThat(new double[] { .1 }, isArrayCloseTo(new double[] { .1 })); //fails

The problem is in TypesafeMatcher, line 65: expectedType.isInstance(item), where expectedType is Object.class and item is [0.1].

I suspect that this problem has to do with the fact that I can't genericize the Matchers array I pass to IsArray, but I don't know how to fix this. Can anyone tell me how I should be matching an array of doubles?


Solution

  • It ultimately goes back to the fact that double[] cannot be cast to Double[], and the same goes for Matcher<double[]> to Matcher<Double[]>.

    If you can change all your uses of primitive double[] to Double[], then your approach works fine. Otherwise, you'll need to write your own custom matcher. I've included below a custom Matcher that builds on your code.

    This is essentially a duplicate of this: How can I use Hamcrest to check if each element in an array of doubles is "close" to each element in another array?

    @SuppressWarnings({"rawtypes", "unchecked"})
    public static Matcher<double[]> isArrayCloseTo(double[] expected) {
        final double DELTA = 1e-10;
        List<Matcher<Double>> matchers = new ArrayList<>();
        for (double d : expected)
            matchers.add(new IsCloseTo(d, DELTA));
    
        return new CustomTypeSafeMatcher<double[]>("array that is close to" + Arrays.toString(expected)) {
            @Override
            protected boolean matchesSafely(double[] actual) {
                return new IsArray<Double>(matchers.toArray(new Matcher[matchers.size()]))
                        .matchesSafely(Arrays.stream(actual).boxed().toArray(Double[]::new));
            }
        };
    }