使用Generics和Mockito的ClassCastException

时间:2013-04-09 15:34:23

标签: java generics mockito classcastexception

我有这个案例使用JDK6,Junit3和Mockito 1.8.5(名称已被更改以使其易于理解):

public abstract class AbstractProcessorTest<P extends AbstractProcessor<T>, T extends AbstractProcess> {

@Mock(answer = CALLS_REAL_METHODS)
protected P processor;

@Mock(answer = RETURN_DEEP_STUBS)
protected T process;

public void setUp(){
 Mockito.initMocks(this);
 // some common configurations
 processor.setProcess(process);
 ...
}

}

public class ProcessorTest extends AbstractProcessorTest<Processor, ProcessAlpha>{    

@Mock
private Service service;

@Override
public void setUp(){
   super.setUp();
   doReturn(service).when(processor).getService();
}

public void testAMethod(){          
   processor.process();
}

}

当我执行testAMethod()测试用例时,我遇到了这个异常:

java.lang.ClassCastException: org.xyz.AbstractProcessor$$EnhancerByMockitoWithCGLIB$$c948b334 cannot be cast to org.xyz.ProcessorA

当我检查这个方法调用时,它说这个方法在类中找不到。另一个奇怪的事情是我在AbstractProcessor.setUp()中调用方法时没有得到任何异常(但它在ProcessorTest.setUp()中失败)

在我创建AbstractProcessorTest类之前没有发生这种情况,因此我认为这是与泛型有关的一些内容以及Mockito代理这些对象的方式,我需要改变策略。

希望这很清楚。 提前谢谢你,

Sebas .-

1 个答案:

答案 0 :(得分:3)

所以我以前与实际问题无关。虽然答案仍然适用,但是当使用深层存根的模拟回答返回模拟时会发生这种异常。

那么真正的问题是什么,它仍然是由泛型和类型擦除引起的。你有两个不同的类:

测试:

public class ProcessorTest extends AbstractProcessorTest<Processor, ProcessAlpha>

测试的父母:

public abstract class AbstractProcessorTest<P extends AbstractProcessor<T>, T extends AbstractProcess> {

@Mock(answer = ...)
protected P processor;
  1. 因此编译器将首先编译AbstractProcessorTest,这样做时会看到P实际上是AbstractProcessor,它会以这种方式编译类。
  2. 然后编译器将编译ProcessorTest,并且会看到P将被解析为Processor,但它不会修改AbstractProcessorTest,因为它已经被编译。在P中,它仍然会考虑到ProcessorTest,因此其字节码可能包含可能的强制转换操作码。
  3. 当您运行它时,根据字段实例化模拟的当前Mockito代码将在字段P processor中看到AbstractProcessor类型而不是您在{{1}中修复的类型Processor }}。当然它会相应地创建模拟。
  4. CCE是在ProcessorTest方法中引发的,因为由于字段ProcessorTest.setUp的通用特性,编译器肯定引入了静默转换操作码。
  5. 同样看起来很奇怪配置模拟来调用实际方法,它可能会导致很多问题,因为模拟没有用状态初始化。也许你想使用间谍?

    希望有所帮助。


    之前的答案实际上没有回答真正的问题。但是对于Mockito深层存根可能仍然有用

    是当前发布的版本(1.9.5)中的Mockito不支持具有深存根(see issue 230)的泛型,因为 Java实现了带有类型擦除的泛型。因此,它只能找到上限,无论是processor还是擦除后已知的其他类型。

      

    泛型更像是编译器而不是运行时的东西。搜索   google关于为什么java人们想要长期使用reified generics的原因   时间。 Neal Gafter在2006年写了这篇文章   http://gafter.blogspot.fr/2006/11/reified-generics-for-java.html,但是   还有其他有趣的读物。

    EDITED vvvvvvvv 2015-01

    然而,编译器在某些特定情况下嵌入了一些关于泛型的数据,在这个类声明Object的示例中,可以读取这两种类型。 使用 clunky 和慢速反射API。该代码存在于Mockito代码的主程序中,应该可以在您的示例中使用,但由于其他问题,它仍然未发布。

    由于Mockito 1.10.x Mockito更加了解泛型,即如果像这个声明的模拟类型嵌入类型或边界,它将使用它们,如果方法有边界,它将使用它们。

    这意味着像这样的代码可以在没有额外存根的情况下工作,即mockito将发现嵌入在字节码中的边界并在可能的情况下模拟它们(不是最终的或原始的):

    public class ProcessorTest extends AbstractProcessorTest<Processor, ProcessAlpha>

    EDITED ^^^^^^^^

    当然还是无法发现运行时声明。例如,在interface UberList<U> extends List<U extends Uber> { U firstUber(); <D extends Driver> D driver(); } uberList = mock(UberList.class, RETURNS_DEEP_STUB); Uber u1 = uberList.iterator().next(); Uber u1 = uberList.firstUber(); Driver d = uberList.driver(); 中,List<Processor> pList;通用类型信息将被删除。唯一可用的信息将是编译器在编译Processor时找到的信息。如果没有显示太多关于如何完成的细节,那么类型信息将被解析为List,因为泛型类型信息Object上限将被编译器重新分配到E(它是隐式的上限,就像你不必写Object)。

    所以,在同一时间你可以投射到想要的类型,只是不要使用深层存根答案,或者如果你有冒险精神,你可以使用更新的深层存根答案编译自己未发行的Mockito版本。< / p>

    希望有所帮助。