Assuming we have the following Service to write Unit Test.
public interface ITestService {
@Transactional
void test(String id);
}
@Service
public class TestServiceImpl implements ITestService {
@Autowired
private ITestDao testDao;
@Autowired
private IOtherService otherService;
@Override
public void test(String id) {
// business logic here
}
}
We want to test TestServiceImpl, but we reference the IOtherService in TestServiceImpl. Regarding to the Unit Test guideline, we should assuming the IOtherService is always working correctly. So we can use Mockito to mock the IOtherService. The code is as following.
In our test case, we want to mock the IOtherService, but as for the ITestDao, we want to use the actual object. So we use @Mock on IOtherService, @Autowired on ITestDao.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application-context-test.xml")
@WebAppConfiguration("src/test/webapp")
public class BpRelationshipServiceTest {
@Mock
IOtherService otherService;
@Autowired
@InjectMocks
ITestService testService;
@Autowired
private ITestDao testDao;
@Test
public void testCURD(){
// test cases here
}
@Before
public void setUp() throws Exception{
MockitoAnnotations.initMocks(this);
when(otherService.someMethod()).thenReturn(value);
}
@After
public void tearUp() {
// recover the data
}
}
But due to the @Transactional annotation on ITestService, Spring will generate a proxy for the TestServiceImpl which the properties are all autowired by Spring. So when we use Mockito @InjectMocks, the injection of Mock Object won’t work as expected.
And the solution is, after MockitoAnnotations.initMocks(this), we get the TargetObject from proxy using Reflection Mechanism, and set the IOtherService on the TargetObject. Here is the code.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application-context-test.xml")
@WebAppConfiguration("src/test/webapp")
public class BpRelationshipServiceTest {
@Mock
IOtherService otherService;
@Autowired
@InjectMocks
ITestService testService;
@Autowired
private ITestDao testDao;
@Test
public void testCURD(){
// test cases here
}
@Before
public void setUp() throws Exception{
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(AopTargetUtils.getTarget(testService), "otherService", otherService);
when(otherService.someMethod()).thenReturn(value);
}
@After
public void tearUp() {
// recover the data
}
}
public class AopTargetUtils {
/**
* 获取 目标对象
*
* @param proxy
* 代理对象
* @return
* @throws Exception
*/
public static Object getTarget(Object proxy) throws Exception {
if (!AopUtils.isAopProxy(proxy)) {
return proxy;// 不是代理对象
}
if (AopUtils.isJdkDynamicProxy(proxy)) {
return getJdkDynamicProxyTargetObject(proxy);
} else { // cglib
return getCglibProxyTargetObject(proxy);
}
}
private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor = h.get(proxy);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
return target;
}
private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(proxy);
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget();
return target;
}
}