使用Mockito模拟泛型类型时的ClassCastException

时间:2014-06-19 08:52:54

标签: java generics mockito classcastexception

我有一个带有通用参数的抽象测试类。

public abstract class AbstractCarTest<G> {
    ...
    @Mock
    protected G carMock;
    ...
}

我已经实施了一个具体的测试类。

public class TruckTest extends AbstractCarTest<Truck> {
    ...
    when(truckFactory.getTruck(anyString()).return(carMock);
    ...
}

方法签名如下所示

public Truck getTruck(String name);

运行TruckTest时,我得到ClassCastException

java.lang.ClassCastException: org.mockito.internal.creation.jmock.ClassImposterizer$ClassWithSuperclassToWorkAroundCglibBug$$EnhancerByMockitoWithCGLIB$$... cannot be cast to com.example.Truck

为什么?我可以用任何方式解决这个问题吗?

3 个答案:

答案 0 :(得分:2)

Mockito在运行时生成你的模拟类。通过子类化给定类并覆盖其所有方法来创建模拟。 (因此没有模拟final类或方法的限制。)在运行时,所有generic types are erased并由其上限类型替换,在AbstractCarTest这是Object,因为你不指定显式上限。因此,Mockito将您的原始类视为:

public abstract class AbstractCarTest {

  @Mock
  protected Object carMock;
}

在运行时,将创建一个扩展Object而不是您想要的Truck类的模拟。 (您无法看到这一点,因为cglib有一个无法直接扩展Object的错误。相反,Mockito正在扩展一个名为ClassWithSuperclassToWorkAroundCglibBug的内部类。)通常,编译器会在编译时发出类型错误泛型类型仍然可用,但在运行时,您会遇到heap pollution

解决方法如下:

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());
}

通过在不使用泛型的情况下定义模拟类型,您可以通过getter访问抽象基类中的模拟,其中模拟类型被正确定义为Truck

答案 1 :(得分:1)

当Mockito不知道它的具体类型时,它似乎不会嘲笑它。

您可以通过执行以下操作来解决问题:

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;
    }
}

答案 2 :(得分:1)

只想改进@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);
    ...
}

这最终消除了对抽象方法的需求。提供给getActualTypeArguments()的索引将取决于Type Argument在声明中的显示位置。在大多数情况下,这将是0,但如果你有这样的事情:

public abstract class AbstractCarTest<A, B, C, G>

并且您希望使用Class G来提供3所需的A = C + A[k-1:n]