Reputation: 703
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.
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.
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
Reputation: 388
you can optimise the accepted response using the class AopTestUtils
that provides the methods:
getTargetObject
to unwrap the top-level proxy if existsgetUltimateTargetObject
to unwrap all levels of proxies if theyUpvotes: 1
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