3

我试图让 Spring 在我的单元测试中注入 EasyMock 模拟。

在我的 applicationContext.xml 中,我有这个:

<bean id="mockService"  class="org.easymock.EasyMock" factory-method="createMock"  name="MockService">
    <constructor-arg index="0" value="my.project.Service"/>
</bean>

在我的单元测试中,我有这个:

@Autowired
@Qualifier("mockService")
private Service service;

public void testGetFoo() {
    Foo foo = new Foo();

    expect(service.findFoo()).andReturn(foo);
    replay(service); // <-- This is line 45, which causes the exception

    // Assertions go here...
}

当我尝试运行我的测试时,我得到了这个堆栈跟踪:

java.lang.ClassCastException: org.springframework.aop.framework.JdkDynamicAopProxy
at org.easymock.EasyMock.getControl(EasyMock.java:1330)
at org.easymock.EasyMock.replay(EasyMock.java:1279)
at TestFooBar.testGetFoo(TestVodServiceLocator.java:45)

我对 Spring 和 EasyMock 都很陌生,但在我看来,错误是由 EasyMock 试图调用它假定为 EasyMock 实例的方法引起的,但实际上是由 Spring 创建的动态代理。据我了解,动态代理仅实现接口中定义的方法,在本例中为 Service 接口。

我不明白的是,从我读到的(也在这里),我试图实现的目标至少似乎是可能的。

所以我的问题是:我没有做什么或者我做错了什么?

4

4 回答 4

6

您还可以创建一个辅助方法来从 Spring 代理中解开 EasyMock 代理以定义预期的行为,然后:

public static <T> T unwrap(T proxiedInstance) {
  if (proxiedInstance instanceof Advised) {
    return unwrap((T) ((Advised) proxiedInstance).getTargetSource().getTarget());
  }

  return proxiedInstance;
}

注意 recusive 调用,因为在最坏的情况下,您在实际目标周围有多个代理。

于 2009-06-02T13:05:36.040 回答
4

解决了!

我在 applicationContext.xml 中忽略了这一点:

<bean id="txProxyAutoCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames">
        <list>

            <value>*Service</value>
            <!--   ^^^^^^^^    
               This is the problem!
            -->
        </list>
    </property>
    <property name="interceptorNames">
        <list>
            <value>txAdvisor</value>
        </list>
    </property>
</bean>

...这会导致 Spring 自动为所有名称以“Service”结尾的 bean 创建代理对象。

我的解决方案是明确列出 bean 而不是使用通配符。然而,这对我来说似乎有点脆弱,所以如果有人知道如何指定FooService 之外的所有 *Service bean,我将不胜感激。

于 2009-05-28T14:49:50.647 回答
1

这里有些奇怪。您显然很快掌握了 Spring 和 EasyMock。自动代理、工厂方法,所有这些都表明您正在深入了解 Spring 的功能。

尽管如此,您将模拟 bean 注入到一个类中还是有点奇怪。你可能有一个很好的理由,但对我来说这是一种代码味道。我建议您只考虑将真实服务连接到您的测试类中,然后根据需要初始化模拟对象。为什么要在 java 中花费 3 行,在 XML 中花费另外 3 行(加上 1 行来重置它们)来创建一个没有依赖关系的模拟对象?只需说服务服务 = (Service)createMock(Service.class)。我建议以您需要的方法创建它们,设定期望,注入,使用它们,然后丢弃它们。在您的模型中,您必须记住每次使用模拟对象时都要重置它,这比创建一个新对象要清晰。

当然,这是一个风格问题,而不是正确性问题,因此请根据需要忽略。

于 2009-05-29T15:30:27.930 回答
1

我知道这个问题很老,但我只是偶然发现它正在寻找类似的问题。

问题是 Spring 不知道模拟对象的类型。您使用的方法如下所示:

public static <T> T createMock(final Class<T> toMock) {
    return createControl().createMock(toMock);
}

Spring 不够聪明,无法从构造函数参数派生 T (至少我上次检查过),所以它认为返回的对象是 type java.lang.Object。结果,创建的代理没有实现my.project.Service,因此无法注入。

因此,答案是告诉 Spring 所需的类型。

于 2014-04-29T11:47:45.937 回答