ClassCastException when mocking generic type using Mockito

徘徊边缘 提交于 2019-12-11 18:57:33

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!