Mockito isA(Class<T> clazz) How to resolve type safety?

19,633

Solution 1

Mockito/Hamcrest and generic classes

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

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

Suggestions:

anyIterable() in Mockito 2.1.0

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

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

Ignore in Eclipse

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

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

Quick Fix with @SuppressWarnings

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

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

Use a generic isA() matcher with TypeToken

This solves the problem for good. But 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 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());
    }
}

Solution 2

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

Solution 3

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

Solution 4

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

Class<Iterable<Integer>> iterableIntegerClass = ?

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

interface IntegerIterable extends Iterable<Integer> {}

then

isA(IntegerIterable.class)

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

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

Mmm tasty...

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

@SuppressWarnings("unchecked")
Share:
19,633
Mike
Author by

Mike

Updated on July 21, 2022

Comments

  • Mike
    Mike almost 2 years

    in my test I have the following line:

    when(client.runTask(anyString(), anyString(), isA(Iterable.class)).thenReturn(...)
    

    isA(Iterable.class) produces warning that it needs unchecked conversion to conform to Iterable<Integer> . What is syntax for that?

    isA(Iterable<Integer>.class)
    isA((Iterable<Integer>)Iterable.class
    

    do not work.

    Any suggestions?