Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 575
Mike Avatar asked Aug 17 '12 19:08

Mike


3 Answers

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());
    }
}
like image 116
Arend v. Reinersdorff Avatar answered Oct 21 '22 22:10

Arend v. Reinersdorff


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
like image 8
Daniel Pryden Avatar answered Oct 21 '22 22:10

Daniel Pryden


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.

like image 4
Garrett Hall Avatar answered Oct 21 '22 21:10

Garrett Hall