@Component
public class PostDistrictFinder {
public String getDistrict(String letter) {
// this will involve some complex lookup into an even more complex Cobol
// based system
return "SOME DISTRICT";
}
}
@Component
public class PostCentral {
@Autowired
private PostDistrictFinder finder;
//This is just a plain annotation to force Spring to make a proxy
@Timed
public void processLetter(String letter) {
// process letter, e.g. calculate whether postage is correct or send out
// invoice, validate address as known etc.
String district = finder.getDistrict(letter);
System.err.println(district);
// forward letter to district
}
}
public class Main {
public static void main(final String[] args) {
final ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/mock-context.xml");
final PostCentral bean = context.getBean(PostCentral.class);
bean.processLetter("");
}
}
Essentially it is just one bean using another bean. Running this code will print "SOME DISTRICT".
Say I have this test, what do you think it prints?:
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class MockServiceTest {
@Mock
private PostDistrictFinder pdf;
@Autowired
@InjectMocks
private PostCentral poc;
@Before
public void setup(){
MockitoAnnotations.initMocks(this);
Mockito.when(pdf.getDistrict(Mockito.anyString())).thenReturn("Mocked value");
}
@Test
public void test() throws Exception {
poc.processLetter("");
}
}
Does it print "SOME DISTRICT" or "Mocked value"? What do you think it prints if you remove the @Timed annotation on the processLetter in the PostCentral class?
Well the above method prints "SOME DISTRICT" and not "Mocked value" with the @Timed annotation on the method and "Mocked value" without the @Timed annotation (note that the result would have been the same had I used @Transactional).
Why is that? The reason is that Spring will create a proxy of the PostCentral class if it is annotated with @Timed (or @Transaction) or any other annotation that causes a proxy to be created. Set a break point in the code and verify if you are in doubt.
How do you get the test to work? You might think that this is enough:
@Before
public void setup(){
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(poc, "finder", pdf);
Mockito.when(pdf.getDistrict(Mockito.anyString())).thenReturn("Mocked value");
}
However, this is not the case, as you are operating on the proxy and not the proxied object. You can however do this:
//REMOVE THE @InjectMocks FROM THE PostCentral (poc) FIELD
@Before
public void setup() throws Exception{
MockitoAnnotations.initMocks(this);
PostCentral pc = (PostCentral) unwrapProxy(poc);
ReflectionTestUtils.setField(pc, "finder", pdf);
Mockito.when(pdf.getDistrict(Mockito.anyString())).thenReturn("Mocked value");
}
//http://forum.springsource.org/showthread.php?60216-Need-to-unwrap-a-proxy-to-get-the-object-being-proxied
public static final Object unwrapProxy(Object bean) throws Exception {
/*
* If the given object is a proxy, set the return value as the object
* being proxied, otherwise return the given object.
*/
if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
Advised advised = (Advised) bean;
bean = advised.getTargetSource().getTarget();
}
return bean;
}
If the above makes sense, then fine. If you want to understand the inner workings of Spring AOP/proxies, then read this http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies.