Search This Blog

Wednesday, August 20, 2008

Easy Mock Class Extensions: Byte code enhancement, Interface elimination, Mocking final classes/methods

Today, I had a very constructive discussion with some colleagues. One of the primary purposes served by interfaces has been to support "Mocking" of tests, especially in cases where there is at most one implementation in a span of time. After thinking of Easy Mock class Extensions, where one could mock a class, I raised the question of the value of the interface in such a case as one could easily mock an implementation which started a very interesting discussion.

Now, with Mock frameworks like JMock or EasyMock, mocking of Classes is possible but they explicitly declare that classes that are declared final or methods that are declared final cannot be mocked. I thought at the time of how the instrumentation api could be leveraged to assist in mocking the final class or a class with final methods.

One typical case, where we propagate the pattern of an interface and implementation pattern is in the DAO layer. If an API is designed as an external API, i.e., others will use the developed API and/or the API will be deployed in unknown environments where specific implementatio might be in use, then the separation of the DAO interface and implementation is justified. In a standard/isolated case, i.e., a case where one has decided on their database provider,the O/R mapping strategy, the value of the overhead of implementing interfaces is diminished as at any real given time, there would typically be only one concrete implementation. So it begs the question as to why the DAO interface is even required?

One argument that I have heard as the case for interfaces has been testability. For example:

Service service = new ServiceImpl();
service.setDAO(new DAOMockImpl()));

In the above example, the DAO is mocked with an implementation of the DAO interface.

The same could easily acheived by mocking the DAOImplementation class itself.

Anyway, I wanted to see if anyone has in fact handled the case of using byte code enhancement to handle the final problem with mocks. I did find an excellent example of the same in Xzajo's Weblog. In the example, present in the Blog, a very descriptive method of how byte code enhancement is utilized to allow proxying of final classes/methods is described. Very very nice! My example, shown below demonstrates how the same can be achieved in a Maven/Easy mock environment and utilizes the code from Xzajo's blog.

One change that I have is that we probably do not want all loaded classes to be devoid of their "final" keyword. For this reason, I propose a method of specifying the packages one would like to be enhanced.

The instrumentation class utilizes the The Byte Code Engineering Library (BCEL) . We could easily change the same to use some other library if required. I have defined a class called FinalizerRemover which implements ClassFileTransformer and the implementation details are shown below:

1 public byte[] transform(ClassLoader loader, String className, Class redefiningClass,

2 ProtectionDomain domain, byte[] bytes) throws IllegalClassFormatException {

3 byte returnedBytes[] = bytes;

5 try {
6 ClassParser parser = new ClassParser(new ByteArrayInputStream(bytes), className);

7 JavaClass clazz = parser.parse();
8 if (instrument(clazz.getPackageName())) {

9 if (clazz.isFinal()) {
10 System.out.println("Changing final modifier of class:" + clazz);

11 clazz.setAccessFlags(clazz.getAccessFlags() & (~Constants.ACC_FINAL));

12 }
13 Method[] methods = clazz.getMethods();

14 for (Method m : methods) {
15 if (m.isPublic() && m.isFinal()) {

16 System.out.println("Transforming Method:" + m);
17 m.setAccessFlags(m.getAccessFlags() & (~Constants.ACC_FINAL));

18 System.out.println("Transformed Method:" + m);
19 }

20 }
21 }
22 returnedBytes = clazz.getBytes();

24 }
25 catch (Exception e) {

26 e.printStackTrace();
27 }
29 return returnedBytes;

30 }

In the above example, I have method check to see whether the class requires transformation or not. I use an argument to the VM -Dtpackages, a comma separated list of packages requiring transformation.

The code is present in a maven project called Finalizer remover producing a jar file called finalizerremover.jar.

A test maven module (FinalizeOverrideProject) is defined that contains DAO's which are final classes. A service layer class refers to the DAO as shown below:

1 public class ProductServiceImpl {

2 private ProductDAOImpl productDAO;
4 public final void setProductDAO(ProductDAOImpl productDAO) {

5 this.productDAO = productDAO;
6 }

8 public Product getProduct(Integer id) {

9 return productDAO.getProduct(id);
10 }

11 }

The ProductDAO mentioned above is an actual implementation and not an interface.
The test code for the ProductServiceImpl is as follows:

1 private ProductDAOImpl productDAOMock;

2 @Before
3 public void setUp() {

4 productDAOMock = EasyMock.createMock(ProductDAOImpl.class);

5 }
6 @Test
7 public void testGetProduct() {

8 ProductServiceImpl impl = new ProductServiceImpl();
9 impl.setProductDAO(productDAOMock);

11 Integer id = 123;
12 Product p = new Product(id, "Porsche Boxster");

14 EasyMock.expect(productDAOMock.getProduct(id)).andReturn(p);

15 EasyMock.replay(productDAOMock);
16 Product result = impl.getProduct(id);

17 assertNotNull(result);
18 EasyMock.verify(productDAOMock);

19 }

If run without the byte code enhancement, the above test would not work as it would not be possible for EasyMock to "mock" the final DAO class. However, as the DAO class is
enhanced to strip out the final methods, mocking is possible.

To enable the instrumentation to work when we run a "mvn test", the following changes are affected to the maven pom to specify the intrumented jar and the packages to be instrumented:


<argLine>-Dtpackages="com.welflex" -javaagent:${settings.localRepository}/com/welflex/finalizeRemover/finalizeremover/1.0-SNAPSHOT/finalizeremover-1.0-SNAPSHOT.jar</argLine>


Maven's surefire plugin is configured to run with the instrumentation jar. The intrumentation jar is obtained from the local maven repo.

The example is run on a Maven 2.0.9, Java 1.6.X environment. In order to run the example issue a
"mvn install" from the FinalizeRemover project. The operation will result in the deployment of
the intrumentation library in a local repository. Execute a "mvn test" on the FinalizeOverrideProject to test and view the example.


Think more about redundant interfaces, orthogonality has a price. More later...

Downloads: The code for the example can be obtained from HERE!

No comments: