I'm using SPring 3.1.1.RELEASE and JUnit 4.8.1. In my test class, I want to mock a private field and discovered the beauty of "ReflectionTestUtils" ...
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:test-context.xml" })
public class OrderServiceTest extends AbstractTransactionalJUnit4SpringContextTests
…
@Autowired
private OrderService m_orderSvc;
@Test
public void testGetPDOrders() throws QuickBaseException, Exception {
…
ReflectionTestUtils.setField(m_orderSvc, "m_orderDao", mockOrderDao);
Below is the class and field I'm trying to mock ...
@Service("orderService")
@Transactional
public class OrderServiceImpl implements OrderService {
…
@Autowired
private OrderDAO m_orderDao;
Disappointingly, I get the below error. I've read that this is because my class is marked as "@Transactional" but I can't find an adequate work-around and it seems like a waste to write setter methods solely to accommodate JUnit. Does anyone have another suggestion about how I can inject my mock object into a private field?
java.lang.IllegalArgumentException: Could not find field [m_orderDao] on target [org.mainco.subco.myclient.service.OrderServiceImpl@282f0e07]
at org.springframework.util.Assert.notNull(Assert.java:112)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:107)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:84)
at org.mainco.subco.myclient.service.OrderServiceTest.testGetPDOrders(OrderServiceTest.java:130)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
I'm not sure what mocking library you're using, but there is a helpful integration between Spring and Mockito aptly and clumsily named "Springockito" that can make tactical insertions of mocks in a greater Spring context pretty easy.
The idea is that you change your test application context to map that bean to a mock, rather than try to wire the mock to your parent bean at runtime.
So really you just end up with:
text-context.xml
<beans xmlns="http://www.springframework.org/schema/beans"
...
xmlns:mockito="http://www.mockito.org/spring/mockito"
xsi:schemaLocation="... http://www.mockito.org/spring/mockito https://bitbucket.org/kubek2k/springockito/raw/tip/springockito/src/main/resources/spring/mockito.xsd">
<mockito:mock id="m_orderDao" class="my.package.OrderDao"/>
<!--... other config ...-->
</beans>
Then you can just autowire the mock itself into your test if you need to interact with it, the same way you are doing for your service as it stands now.
If you're not using Mockito, you can still use the approach above, but instead use your mocking library's method for creating mocks as the factory for the bean.