Tuesday, October 21, 2014

Testing Old/Legacy Code using Powermock

I am sharing some of my finding/learning during my attempts to write tests for some codes that were not quite designed for writing tests

Breaking the dependency

You will probably find plenty of dependencies that will be very difficult to satisfy from your unit test which can be very demotivating to write tests. This link has a good description on how to break such dependencies in legacy code. Is shows how to separate the DB connection layer from the service to help making the class more testable. One approach I have taken when you have codes like this  ServiceFactory.getService("MyService"), in the method you are testing is to pass in MyService to the class file via its constructor so that you can easily pass in a mocked MyService.

Static method calls

Static method calls that are in the method that you are trying to test can be very annoying. Use PowerMock's mockStatic method to mock such calls. This link has a good explanation of how to do this. In summary, declare the class with the static method at the class level with PrepareForTest annotation e.g. @PrepareForTest({JOptionPane.class, MyFactory.class}) and do the rest in your test method. An example is below -

        PowerMock.mockStatic(JOptionPane.class);
        JOptionPane.showMessageDialog((Component) anyObject(), (String) anyObject(), (String) anyObject(), anyInt());
        expectLastCall().once();
        PowerMock.replay(JOptionPane.class);



Capturing arguments

 At times capturing the argument and verifying the content is the only thing you can test and you can do this the following way.

        Capture capturedArgument = new Capture();
        mySerivce.updateMyListIdDb(and(capture(capturedArgument), isA(List.class)));

       
        //replay
        //test method call
        assertEquals(capturedArgument.getValue().size(), 1);
        MyListClass listClass = (MyListClass) capturedArgument.getValue().get(0);
        assertEquals(listClass.getValue(), "myExpectedValue");


A cleaner way to do this with latest easy mock is as below:
        Capture<List> capturedArgument = new Capture<List>();
        mySerivce.updateMyListIdDb(capture(capturedArgument)));

        //replay
        //test method call
        List myList = capturedArgument.getValue();
        /assert

VerifyError

I ran into this error when a static method was invoked on a class which in turn has a static field  which needed to be loaded first. I am not exactly sure why this would happen.

java.lang.VerifyError: (class: javax/swing/plaf/metal/MetalLookAndFeel, method: getLayoutStyle signature: ()Ljavax/swing/LayoutStyle;) Wrong return type in function
    at javax.swing.UIManager.setLookAndFeel(UIManager.java:560)
    at javax.swing.UIManager.initializeDefaultLAF(UIManager.java:1329)


Decorate your test class with @PowerMockIgnore("javax.swing.*")

This defers the loading of class or classes provided in the annotation to the systems class loader. If there are multiple classess you would like to defer then try this - @PowerMockIgnore({"javax.swing.JCheckBox", "com.apple.laf.*"})

No comments: