[ACCEPTED]-Mockito isA(Class<T> clazz) How to resolve type safety?-java-5

Accepted answer
Score: 31

Mockito/Hamcrest and generic classes

Yes, this is a general problem with Mockito/Hamcrest. Generally 16 using isA() with generic classes produces a warning.

There 15 are predifined Mockito matchers for the 14 most common generic classes: anyList(), anyMap(), anySet() and anyCollection().

Suggestions:

anyIterable() in Mockito 2.1.0

Mockito 13 2.1.0 added a new anyIterable() method for matching Iterables:

when(client.runTask(anyString(), anyString(), anyIterable()).thenReturn(...)

Ignore in Eclipse

If 12 you just want to get rid of the warning 11 in Eclipse. Option exists since Eclipse Indigo:

Window 10 > Preferences > Java > Compiler 9 > Errors/Warnings > Generic types 8 > Ignore unavoidable generic type problems

Quick Fix with @SuppressWarnings

I 7 suggest you do this if you have the problem 6 only once. I personally don't remember ever 5 needing an isA(Iterable.class).

As Daniel Pryden says, you can 4 limit the @SuppressWarnings to a local variable or a helper 3 method.

Use a generic isA() matcher with TypeToken

This solves the problem for good. But 2 it has two disadvantages:

  • The syntax is not too pretty and might confuse some people.
  • You have an additional dependency on the library providing the TypeToken class. Here I used the TypeToken class from Guava. There's also a TypeToken class in Gson and a GenericType in JAX-RS.

Using the generic 1 matcher:

import static com.arendvr.matchers.InstanceOfGeneric.isA;
import static org.mockito.ArgumentMatchers.argThat;

// ...

when(client.runTask(anyString(), anyString(), argThat(isA(new TypeToken<Iterable<Integer>>() {}))))
            .thenReturn(...);

Generic matcher class:

package com.arendvr.matchers;

import com.google.common.reflect.TypeToken;
import org.mockito.ArgumentMatcher;

public class InstanceOfGeneric<T> implements ArgumentMatcher<T> {
    private final TypeToken<T> typeToken;

    private InstanceOfGeneric(TypeToken<T> typeToken) {
        this.typeToken = typeToken;
    }

    public static <T> InstanceOfGeneric<T> isA(TypeToken<T> typeToken) {
        return new InstanceOfGeneric<>(typeToken);
    }

    @Override
    public boolean matches(Object item) {
        return item != null && typeToken.getRawType().isAssignableFrom(item.getClass());
    }
}
Score: 8

Here's what I do:

// Cast from Class<Iterable> to Class<Iterable<Integer>> via the raw type.
// This is provably safe due to erasure, but will generate an unchecked warning
// nonetheless, which we suppress.
@SuppressWarnings("unchecked")
Class<Iterable<Integer>> klass 
    = (Class<Iterable<Integer>>) (Class) Iterable.class;  

// later

isA(klass) // <- now this is typesafe

0

Score: 4

You can add @SuppressWarnings("unchecked") above the statement. No other 2 way but if it bothers you, you can move 1 the cast to a helper method.

Score: 2

There is no way to do this. To simplify, you 9 can't initialize this variable without warning 8 :

Class<Iterable<Integer>> iterableIntegerClass = ?

One solution might be to use the pseudo-typedef antipattern, ,you 7 create and use an IntegerIterable interface

interface IntegerIterable extends Iterable<Integer> {}

then

isA(IntegerIterable.class)

will no 6 more produce warning. But you will have 5 to extend the class implementing Iterable to let 4 them implements IntegerIterable :) For example :

public class IntegerArrayList extends ArrayList<Integer> implements IntegerIterable {}

Mmm tasty...

So, i 3 will sugest you to consider to just paper 2 over the cracks by adding to your method 1 :

@SuppressWarnings("unchecked")

More Related questions