Search code examples
javaunit-testingjunitmockitojunit5

Mockito's reified mock method typing the mock as the superclass instead of generic type


I want to provide a simpler interface (utility method) to mock a construction of a class we often need mocked in many tests because it's constructed by a third party library. This class is generic typed and has a method that return it's generics (ClassIWantMockedConstructor<T> { whatever(): T }). The utility method is something like this:

public interface Execution<T extends ParentClass> {
    void execute(T mock) throws Exception;
}
    
public static <T extends ParentClass> void withMockedConstruction(Execution<T> execution) {
    T mock = mock();
    try (@SuppressWarnings("unused") MockedConstruction<?> construction = mockConstruction(
            ClassIWantMockedConstructor.class,
            (mocked, ctx) -> when(mocked.whatever()).thenReturn(mock)
    )) {
        execution.execute(mock);
    } catch (Throwable t) {
        throw new RuntimeException("Unexpected error during tests, please check the tests", t);
    }
}

However, if I use withMockedConstruction passing an lambda typed with a child class of ParentClass, then T is still typed as ParentClass. Which will cause a ClassCastException later down the line. Example:

// When called like this
withMockedConstruction((ChildClass mockService) -> { ... });

// Then debugging
public static <T extends ParentClass> void withMockedConstruction(Execution<T> execution) {
    T mock = mock(); // mock = Mock for ParentClass, T reified as ParentClass
    ...
        execution.execute(mock); // throws ClassCastException
}

The only way I could overcome this was by specifying manually a class for the mock, thus changing the signature to require the class as following:

// Change signature to this:
public static <T extends ParentClass> void withMockedConstruction(Class<T> clazz, Execution<T> execution) {
    T mock = mock(clazz); // mock = Mock for ChildClass normally
    ...
}

// Calling like this, works as intended
withMockedConstruction(ChildClass.class, mockService -> { ... });

Is there any way to achieved this result by using only refied? Can I force it somehow to check the refied type of a lambda function any way? Is using the class the best solution in this scenario?


Solution

  • The "trick" being used to reify generics does not work transitively: you cannot yourself use generics and expect the trick to work.

    public static <T extends ParentClass> void withMockedConstruction(
          Execution<T> execution) {
        T mock = mock();
    

    At runtime, T is erased to ParentClass, and therefore mock() will reify ParentClass, not T. (I suppose the implication is that this is a specific case where @SafeVarargs is actually wrong.)

    You could perhaps identify the parameter type of the lambda, potentially with a custom functional interface with a default method adopting a trick like this one, but at that point you're not saving much of anything compared to simply passing the Class, since you must always specify the lambda argument type using parentheses.