Kenco
Kenco

Reputation: 703

Spring Bean Injection Failing Due To Proxy

Spring Version: 3.2.4.RELEASE and 3.2.9.RELEASE

Mockito Version: 1.8.5

I've been trying to introduce H2 tests to an old project for integration testing, and I've been running into a few issues. Due to the way transactions were propagating, I needed to mock out an autowired class. I've done this before, but I'm now running into severe problems. The following error message is being thrown when initialising the test:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.stuff.XMLITCase': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'TheProcessor' must be of type [com.stuff.XMLBatchFileProcessor], but was actually of type [$Proxy118] at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:307)

Diving into this a bit deeper, it turns out the the bean is in-fact a proxy. If we check the AbstractBeanFactory (round line 239), we can see the proxy:

sharedInstance = {$Proxy117@7035} "com.stuff.XMLBatchFileProcessor@66c540d0" h = {org.springframework.aop.framework.JdkDynamicAopProxy@7039}

The only problem is, I've no clue where this is coming from. I've gone over the config and dependencies, and can't find anywhere that this should be happening.

Project Setup

Unfortunately I can't give a sample project for this, but I'll go over my test configuration. I have a root class that I extend for the tests:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/spring/spring-test-context.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public abstract class AbstractIntegrationTest {
}

This simply loads in some spring config and rolls back the transactions after each test.

The spring config is nothing strange either, though there is one difference between my other module and this one. This is the transaction manager and session factory:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="hibernateSessionFactory"/>
</bean>

<bean id="hibernateSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
...
</bean>

In my other module, I'm using an entityManagerFactory, and a different transaction manager:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
</bean>

The actually class has some autowired fields, and the usual @Service annotation:

@Service(value = "TheProcessor")
public final class XMLBatchFileProcessor extends BatchFileProcessor implements IXMLBatchProcessor {

Finally, the actual test is as follows:

public class XMLITCase extends AbstractIntegrationTest {

    @Resource(name = "TheProcessor")
    @InjectMocks
    private XMLBatchFileProcessor xmlProcessor;

    @Mock
    private ProcessHelper processHelper;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void test() throws Exception {
        Assert.assertNotNull(xmlProcessor);
    }

}

If I replace the XMLBatchFileProcessor with the interface and autowire the field, then there aren't any problems compiling. Mockito, however, never replaces the autowired bean with the mocked object. If it did, then I wouldn't bother with the @Resource annotations and naming the service, thus avoiding the Proxy issue.

Any assistance on this would be appreciate. I'll be focusing on the session factory and the differences there, but it's quite possible that I'm missing something else entirely.

EDIT

Going on Sotirios' comment, I had another look this morning and indeed had missed that the xmlProcessor has a @Transactional annotation, thus meaning that the class needs to be proxied. If I remove the final declaration and let CGLib enhance it, then Mockito does replace the bean when initMocks(this) this called. When a method is called, however, CGLib seems to replace all the beans with Spring enhanced versions, hence overwriting the Mockito version.

What is the correct way to use both Mockito and Spring in an integration test for a class with @Transactional annotations?

Upvotes: 3

Views: 8896

Answers (2)

wannas
wannas

Reputation: 388

you can optimise the accepted response using the class AopTestUtils that provides the methods:

  • getTargetObject to unwrap the top-level proxy if exists
  • getUltimateTargetObject to unwrap all levels of proxies if they
    exist

Upvotes: 1

Kenco
Kenco

Reputation: 703

Alright, once I realised that the class was being proxied due to the @Transactional annotation, the solution to the problem became clearer. What I needed to do was unwrap the proxy, and set the mocked object directly on that:

So in my AbstractIntegrationTest:

/**
 * Checks if the given object is a proxy, and unwraps it if it is.
 *
 * @param bean The object to check
 * @return The unwrapped object that was proxied, else the object
 * @throws Exception
 */
public final Object unwrapProxy(Object bean) throws Exception {
    if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
        Advised advised = (Advised) bean;
        bean = advised.getTargetSource().getTarget();
    }
    return bean;
}

Then in my @Before:

@Mock
private ProcessHelper processHelper;

@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);

    IXMLBatchProcessor iXMLBatchProcessor = (IXMLBatchProcessor) unwrapProxy(xmlProcessor);
    ReflectionTestUtils.setField(iXMLBatchProcessor , "processHelper", processHelper);
}

This left all the @Autowired classes intact, while injecting the correct mocked object.

Upvotes: 14

Related Questions