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?
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.