【问题标题】:Using Spring to inject EasyMock mocks causes ClassCastException使用 Spring 注入 EasyMock 模拟导致 ClassCastException
【发布时间】:2009-05-28 10:58:26
【问题描述】:

我正在尝试让 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 实例的方法引起的,但实际上是由创建的动态代理春天。据我了解,动态代理仅实现接口中定义的方法,在本例中为 Service 的接口。

我不明白的是from what I read(也是here),我试图实现的至少似乎是可能的。

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

【问题讨论】:

    标签: unit-testing spring dependency-injection mocking easymock


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

      解决了!

      我在 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 而不是使用通配符。然而,这对我来说似乎有点脆弱,所以如果有人知道如何指定所有 *Service beans except FooService,我将不胜感激。

      【讨论】:

        【解决方案3】:

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

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

        当然,这是一个样式问题,而不是正确性问题,所以请随意忽略。

        【讨论】:

        • 我在发布之前稍微简化了我的示例,以使其更易于理解。问题是被测类恰好负责在运行时根据其名称从应用程序上下文中获取特定的 bean。将 bean 定义为 xml 文件中的 mock 似乎很明显。不过,我理解你的观点,你是对的。我想我把一些东西混在一起了,走得太远了。
        • 如果您想要集成测试建议执行或接线,这将变得很有用。这样你可以例如检查交易是否正确应用。
        【解决方案4】:

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

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

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-06-16
          • 1970-01-01
          相关资源
          最近更新 更多