2
votes

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?

2
Show us the code of XMLBatchFileProcessor. Does it have @Transactional annotations? What is BatchFileProcessor? The typical solution would be to have the proxying happen with CGLIB, but that won't work because your class is final. - Sotirios Delimanolis
The method I'd be calling doesn't specifically have @Transactional annotations (though some autowired services do - hence the need to mock those out), but the test itself does. The BatchFileProcessor is just a class with some common methods and constants. I'll add more context for those classes tomorrow when I can. - Kenco
You should be programming to interfaces not concrete classes. I.e. instead of XMLBatchFileProcessor make the field a IXMLBatchProcessor - M. Deinum
I mentioned that in the second last paragraph - if I replace the concrete implementation with the interface, then Mockito never replaces the autowired bean with the mocked one. - Kenco
@SotiriosDelimanolis You were correct, the class had a @Transactional annotation, which caused it to be proxied. I've amended my question to find out the correct way to test this. - Kenco

2 Answers

13
votes

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.

1
votes

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