2

我有这个使用 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 代理这些对象的方式相关的东西,我需要改变我的策略。

希望这已经足够清楚了。先感谢您,

塞巴斯.-

4

1 回答 1

4

所以我以前的问题与实际问题无关。尽管答案仍然成立,但当使用深度存根答案的模拟返回模拟时,就会发生这种异常。

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

考试:

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而不是Processor您修复的类型ProcessorTest。当然,它会相应地创建模拟。
  4. 在该ProcessorTest.setUp方法中提出了 CCE,因为由于该字段的通用性质,编译器肯定引入了静默强制转换操作码processor

此外,将模拟配置为调用真实方法看起来很奇怪,这可能会导致许多问题,因为模拟没有使用状态进行初始化。也许您想改用间谍?

希望有帮助。


以前的答案实际上并没有回答真正的问题。但仍可能证明对 Mockito 深存根有帮助

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

泛型更像是编译器的东西而不是运行时的东西。搜索谷歌,了解为什么 java 人长期以来都喜欢物化泛型。Neal Gafter 在 2006 年写了一篇关于它的文章 http://gafter.blogspot.fr/2006/11/reified-generics-for-java.html,但还有其他有趣的读物。

已编辑 vvvvvvvv 2015-01

但是,编译器在某些特定情况下嵌入了一些有关泛型的数据,在您的示例中,使用此类声明public class ProcessorTest extends AbstractProcessorTest<Processor, ProcessAlpha>可以读取这两种类型。使用笨重且缓慢的反射 API。该代码存在于 Mockito 代码大师中,应该可以在您的示例中运行,但由于其他问题仍未发布。

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

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

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编译器解析(这是一个隐式上限,就像您不必编写一样extends Object)。

因此,与此同时,您可以转换为想要的类型,根本不使用深度存根答案,或者如果您喜欢冒险,您可以使用更新的深度存根答案为自己编译一个未发布的 Mockito 版本。

希望有帮助。

于 2013-04-09T16:47:08.243 回答