Search code examples
javajunithamcrest

Why does hamcrest say that a byte 0 is not equal to an int 0?


Consider the following test case using standard JUnit asserts and hamcrest's assertThat:

byte b = 0;
int i = 0;

assertEquals(b, i); // success
assertThat(b, equalTo(i)); // java.lang.AssertionError: Expected: <0> but: was <0>

if (b == i) {
    fail(); // test fails, so b == i is true for the JVM
}

Why is that so? The values are apparently equal for the JVM because b == i is true, so why does hamcrest fail?


Solution

  • Assert#assertThat is a generic method. Primitive types don't work with generics. In this case, the byte and int are boxed to Byte and Integer, respectively.

    It then becomes (within assertThat)

    Byte b = 0;
    Integer i = 0;
    
    b.equals(i);
    

    Byte#equals(Object)'s implementation checks if the argument is of type Byte, returning false immediately if it isn't.

    On the other hand, assertEquals is Assert#assertEquals(long, long) in which case both the byte and int arguments are promoted to long values. Internally, this uses == on two primitive long values which are equal.


    Note that this boxing conversion works because assertThat is declared as

    public static <T> void assertThat(T actual, Matcher<? super T> matcher) {
    

    where the byte is boxed to a Byte for T, and the int is a boxed to an Integer (within the call to equalTo), but inferred as a Number to match the Matcher<? super T>.

    This works with Java 8's improved generic inference. You'd need explicit type arguments to make it work in Java 7.