使用 Spring 注入 EasyMock 模拟会导致 ClassCastException

发布于 2024-07-21 16:48:37 字数 1412 浏览 6 评论 0原文

我试图让 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 的接口。

我不明白的是我读到的内容 (还有这里),我想要实现的至少似乎是可能的。

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

I am trying to get Spring to inject EasyMock mocks in my unit tests.

In my applicationContext.xml, I have this:

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

In my unit test I have this:

@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...
}

When I try to run my test, I get this stack trace:

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)

I am quite new to both Spring and EasyMock, but it seems to me that the error is caused by EasyMock trying to call a method on what it assumes to be an instance of EasyMock, but is in reality a dynamic proxy created by Spring. As I understand it, dynamic proxies only implement the methods defined in the interface, in this case the interface for Service.

What I don't understand is that from what I read (also here), what I'm trying to achieve at least seems to be possible.

So my question is: What I'm I not doing or what am I doing wrong?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

情泪▽动烟 2024-07-28 16:48:38

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

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

  return proxiedInstance;
}

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

You can also create a helper method to unwrap the EasyMock proxy from the Spring proxy to define expected behaviour then:

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

  return proxiedInstance;
}

Note the recusive call as in worst case you have multiple proxies wrapped around the actual target.

时光病人 2024-07-28 16:48:38

解决了!

我在 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 beans,我将不胜感激。

Solved!

I had overlooked this in my 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>

... which causes Spring to automatically create proxy objects for all beans with names that end in "Service".

My solution was to explicitly list the beans instead of using a wild card. This seems a little brittle to me, however, so if anyone knows how to specify all *Service beans except FooService, I would be grateful.

仙女 2024-07-28 16:48:38

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

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

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

Something's odd here. You're clearly quickly mastering both Spring and EasyMock. The autoproxies, the factory methods, all good signs that you're diving deep into Spring's capabilities.

Still, it's kind of odd that you're injecting a mock bean into a class at all. You may have a great reason, but to me it's a code smell. I'd advise you to consider only wiring in real services into your test classes and then initializing the mock objects as needed. Why spend 3 lines in java and another 3 lines in XML (plus 1 line to reset them) to create a mock object with no dependencies? Just say Service service = (Service)createMock(Service.class). I would advise creating them in the methods that you need them for, set expectations, inject, use them, and then discard them. In your model, you'll have to remember to reset the mock object every time you use it, which is less clear than just creating a new one.

Of course, this is a style issue and not a correctness issue, so ignore as desired.

走走停停 2024-07-28 16:48:38

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

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

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

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

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

I know this question is old, but I just stumbled upon it searching for a similar problem.

The problem is that Spring doesn't know the type of the mock object. The method you use looks like this:

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

Spring isn't smart enough to derive T from the constructor argument (at least the last time I've checked), so it thinks the returned object is of type java.lang.Object. As a consequence, the created proxy doesn't implement my.project.Service and therefore can't be injected.

The answer therefore is to tell Spring the required type.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文