Intercept a Method Invocation with AOP
8.2.4. AOP with Mocks - and intercepting a method invocation is just what aop does best. Our jboss-aop.xml file: The pointcut expression intercepts the factorycall bank.BankAccountDAOFactory.getBankAccountDAO*() and calls the interceptor bank.BankAccountDAOInterceptor. package bank; import org.jboss.aop.joinpoint.Invocation; import org.jboss.aop.joinpoint.MethodInvocation; import org.jboss.aop.advice.Interceptor; import util.MockService; public class BankAccountDAOInterceptor implements Interceptor { public String getName() { return "BankAccountDAOInterceptor"; } public Object invoke(Invocation invocation) throws Throwable { try { MockService mockService = MockService.getInstance(); Object mock = mockService.getMockForInterface( "BankAccountDAO"); if(mock == null) { System.out.println("ERROR: BankAccountDAOInterceptor didnt find class!"); // this will probably fail, but its the sainest thing to do return invocation.invokeNext(); } return mock; } finally { } } } Instead of returning invocation.invokeNext(), we ignore the invocation stack since we want to replace the invocation call with a mock implementation. The interceptor receives the invocation and get an instance of the singleton MockService. The use of MockService may not be clear, but we want the test to instanciate the mock objects. That way, the test can easily modify the input to the methods we want to test. The test creates an object of the mock and put it into the MockService with the interface name as the key. The Interceptor then tries to get the mock from MockService and return it. package util; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.Set; public class MockService { private static MockService instance = new MockService(); private Map mockReferences = null; protected MockService() { mockReferences = new Hashtable(); } public static MockService getInstance() { return instance; } public void addMock(String c, Object mock) { mockReferences.put(c, mock); } public Object getMockForInterface(String myKey) { Set keys = mockReferences.keySet(); for (Iterator iter = keys.iterator(); iter.hasNext();) { String key = (String) iter.next(); if(myKey.equals(key)) { return mockReferences.get(key); } } return null; } } Everyting is now in place to write the test. Note that much of this setup code is written once and it will be reused by all similar tests. Then the test: BankBusinessTestCase package bank; import junit.framework.TestCase; import customer.Customer; import util.MockService; public class BankBusinessTestCase extends TestCase { private MockBankAccountDAO mock; private Customer customer; public BankBusinessTestCase(String name) { super( name); } public void setUp() { mock = new MockBankAccountDAO(); BankAccount account = new BankAccount( 10); account.setBalance( 100); BankAccount account2 = new BankAccount( 11); account2.setBalance( 500); mock.setupGetBankAccount( account); mock.setupGetBankAccount( account2); MockService mockService = MockService.getInstance(); mockService.addMock( "BankAccountDAO", mock); customer = new Customer("John", "Doe"); customer.addAccount( new Long(10)); customer.addAccount( new Long(11)); } public void testSumOfAllAccounts() { BankBusiness business = new BankBusiness(); double sum = business.getSumOfAllAccounts( customer); assertEquals((double) 600, sum); System.out.println("SUM: "+sum); } } To compile and run the test we call ant compile test. Output from the test: test-bankbusiness: [junit] .SUM: 600.0 [junit] Time: 0,23 [junit] OK (1 test) The testresult was exactly what we expected. With the the use of AOP we can test every aspect of our code. This example show the limits of object-oriented programming (OOP) compared to AOP. It must be pointed out that it is possible to write these tests without AOP, but it would require to edit production code just to make the tests pass. The approach in this example can easily be used to mock SessionBeans instead of a DAO layer. Theoretically, we can test all of the business methods in a large J2EE application outside the container. This would greatly increase quality and speed during software development.