Saturday, December 29, 2012

Mockito and Spring proxies

I use Spring and I use Mockito. Mockito is extremely good at keeping unit tests fast but of course it is also beneficial to sometimes test by wiring up Spring (integration tests). However, when I do integration tests I sometimes only want to wire up part of the world and mock out other parts. Let me introduce some code before continuing. Say we have this code:

@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.

1 comment:

  1. The effectiveness of IEEE Project Domains depends very much on the situation in which they are applied. In order to further improve IEEE Final Year Project Domains practices we need to explicitly describe and utilise our knowledge about software domains of software engineering Final Year Project Domains for CSE technologies. This paper suggests a modelling formalism for supporting systematic reuse of software engineering technologies during planning of software projects and improvement programmes in Final Year Project Centers in Chennai.

    Spring Framework has already made serious inroads as an integrated technology stack for building user-facing applications. Spring Framework Corporate TRaining the authors explore the idea of using Java in Big Data platforms.
    Specifically, Spring Framework provides various tasks are geared around preparing data for further analysis and visualization. Spring Training in Chennai

    ReplyDelete