问题
I have a abstract test class with a generic parameter.
public abstract class AbstractCarTest<G> {
...
@Mock
protected G carMock;
...
}
I've implemented a concrete test class of it.
public class TruckTest extends AbstractCarTest<Truck> {
...
when(truckFactory.getTruck(anyString()).return(carMock);
...
}
The method signature looks like this
public Truck getTruck(String name);
When running TruckTest
I get a ClassCastException
saying
java.lang.ClassCastException: org.mockito.internal.creation.jmock.ClassImposterizer$ClassWithSuperclassToWorkAroundCglibBug$$EnhancerByMockitoWithCGLIB$$... cannot be cast to com.example.Truck
Why is that? Any way I can work around that?
回答1:
Mockito is generating your mock class at runtime. Mocks are created by subclassing a given class and by overriding all of its methods. (Thus the limitation of not mocking final
classes or methods.) At runtime, all generic types are erased and replaced by their upper type bound, in your AbstractCarTest
this is Object
as you do not specify an explicit upper bound. Therefore, Mockito sees your raw class as:
public abstract class AbstractCarTest {
@Mock
protected Object carMock;
}
at runtime and will create a mock that extends Object
instead of your desired Truck
class. (You cannot see this because cglib has a bug where it cannot extend Object
directly. Instead, Mockito is extending an internal class called ClassWithSuperclassToWorkAroundCglibBug
.) Usually, the compiler would issue a type error at compile time where the generic type is still available but at runtime, you experience heap pollution instead.
A work around would be as follows:
public abstract class AbstractCarTest<T> {
protected abstract T getCarMock();
// define some base test cases here that use the generic type.
}
public class TruckTest extends AbstractCarTest<Truck> {
@Mock
private Truck carMock;
@Override
protected Truck getCarMock() { return carMock; }
// ...
when(truckFactory.getTruck(anyString()).return(getCarMock());
}
By defining your mocked type without using generics, you are able to access the mock from the abstract base class by a getter where the mocked type is correctly defined as Truck
.
回答2:
It seems like Mockito will not mock a class when it does not know it's concrete type.
You can get around the problem by doing the following:
public abstract class AbstractCarTest<G> {
...
protected G carMock;
...
@Before
public void setUp() throws Exception {
carMock= Mockito.mock(getClazz());
}
protected abstract Class<G> getClazz();
}
public class TruckTest extends AbstractCarTest<Truck> {
...
when(truckFactory.getTruck(anyString()).return(carMock);
...
@Override
protected Class<Truck> getClazz() {
return Truck.class;
}
}
回答3:
Just wanted to improve a bit on the answer from @geoand.
public abstract class AbstractCarTest<G> {
...
protected G carMock;
...
@Before
public void setUp() throws Exception {
carMock= Mockito.mock(getClazz());
}
private Class<G> getClazz() {
return (Class<G>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
}
public class TruckTest extends AbstractCarTest<Truck> {
...
when(truckFactory.getTruck(anyString()).return(carMock);
...
}
This ends up removing the need for the abstract method. The index supplied to getActualTypeArguments()
will depend on where the Type Argument appears in your declaration. In most cases this will be 0
however if you have something like this:
public abstract class AbstractCarTest<A, B, C, G>
And you wanted to use the Class
of G
you would need to supply and index of 3
.
来源:https://stackoverflow.com/questions/24302535/classcastexception-when-mocking-generic-type-using-mockito