JMock and AspectJ

I think it is no secret that I have become obsessed with unit testing, more specifically the mockist theory of testing. Unfortunately over the last two weeks I have come across a scenario that JMock does not allow me to test very easily.

The scenario is basically when you instantiate an object inside of a method to preform some action. This problem is only occuring because I am not using a factory to get my objects. If I were using a factory, I could inject a mock factory and all would be wonderful. Now we can debate back and forth whether I should use a factory or not, but at the end of the day it doesn’t matter, because this scenario will occur at some point. Descriptions are a lot harder than code, here is what I am talking about:


public List getPeopleHiredOn(Date hireDate)
{
   PersonSearch search = new PersonSearch();
   return search.by(hireDate);
} 

Just for good measure I will throw in a second scenario:


public void sendInvitationEmail(String address)
{
   InvitationEmail email = new InvitationEmail();
   email.sendTo(address);
   email.send();
} 

Going forward I am going to focus on the first example.

I want to point out that this situation seems to occur in the service layer of my application. The scenario is usually when I am creating a domain object to handle a request from an input to the API. However, I am sure it can occur in other places as well.

JMock gives you many options for manipulating your code, but it doesn’t do you any good unless there is a object you can inject at some point into the process. In this case, I have no such object, because the object is being created within the method I want to test. Enter AspectJ.

With AspectJ I gain the ability to intercept the creation of the PersonSearch object and replace it with a mock. My goal is to have the final test method look like this:


public class PersonServiceTest
{
...
   @Test
   public void shouldExecuteNewPersonSearchWithSuppliedDate()  
   {
      final Date hireDate = new Date();
      final List results = new ArrayList();
      PersonService service = new PersonService();
      
      mockery.checking(new Expectations(){{
         one(personSearch).by(hireDate);
         will(returnValue(results));
      }});
      
      List matchingPeople =  service.getPeopleHiredOn(hireDate);

      assertEqual(matchingPeople, results);
   }
...
}

Assuming that you have an understanding of JMock, the only thing that should look out of place would be that I never declare or initialize personSearch. This is where the magic comes in, I declare the personSearch as static and initialize it in a setup method.


public class PersonServiceTest
{
   private Mockery mockery = new Mockery() {{setImposteriser(ClassImposteriser.INSTANCE);}};
   private static PersonSearch personSearch;

   @Before
   public void resetMocks()
   {
      personSearch = mockery.mock(PersonSearch.class);
   }
...

Because I needed to extend a concrete class, I had to add the setImposteriser to the declaration of my mockery, but that isn’t really important in this example. The only unusual thing I have done is use a setup method to create my mocks instead of doing them inline and declaring them as static. I will get to the static declaration in a second.

The next step is to create our Aspect. In AOP, an Aspect is a collection of pointcuts and advise that you want to occur in your program. My Aspect looks like this:


public class PersonServiceTest
{
...

   @Aspect
   static class TestAspects
   {
      @Pointcut("call(com.danielroop.personservice.PersonSearch.new(..))")
      public void interceptPersonSearchConstructor(){}

      @Around("interceptPersonSearchConstructor()")
      public PersonSearch interceptConstructorForPersonSearch()
      {
         return personSearch;
      }
   }
}

The first thing to note, is that I used the AspectJ 5 annotation support. Next you should notice that I created this as a sub class of my test class. On a side note, I attempted making the test case an Aspect but I ran into conflicts between the JUnit and AspectJ @Before and @After annotations, because apparently AspectJ doesn’t check the type of annotation, just the name. Anyways, this is where the static personSearch is important. In order to create a subclass Aspect like I did here, you have to make it a static class, so in order to access personSearch from the parent you must make it static. I am not sure why the Aspect needs to be static, but the compiler told me so, so I didn’t argue.

That is it, at this point whenever new PersonSearch() gets called any where in this test case it will replace it with a version of my mock PersonSearch, that I can then set expectations on in my test method.

I am a little disappointed with a few of the hoops I needed to jump through, and wish that JMock incorporated Aspects into their expectations, but for now we have to make due with the tools we have.

The final class would look like this:


public class PersonServiceTest
{
   private Mockery mockery = new Mockery() {{setImposteriser(ClassImposteriser.INSTANCE);}};
   private static PersonSearch personSearch;

   @Before
   public void resetMocks()
   {
      personSearch = mockery.mock(PersonSearch.class);
   }

   @Test
   public void shouldExecuteNewPersonSearchWithSuppliedDate()  
   {
      final Date hireDate = new Date();
      final List results = new ArrayList();
      PersonService service = new PersonService();
      
      mockery.checking(new Expectations(){{
         one(personSearch).by(hireDate);
         will(returnValue(results));
      }});
      
      List matchingPeople =  service.getPeopleHiredOn(hireDate);

      assertEqual(matchingPeople, results);
   }

   @Aspect
   static class TestAspects
   {
      @Pointcut("call(com.danielroop.personservice.PersonSearch.new(..))")
      public void interceptPersonSearchConstructor(){}

      @Around("interceptPersonSearchConstructor()")
      public PersonSearch interceptConstructorForPersonSearch()
      {
         return personSearch;
      }
   }
}
This entry was posted in testing and tagged , , , , . Bookmark the permalink.

15 Responses to JMock and AspectJ

Leave a Reply

Your email address will not be published.