Monday, June 21, 2010

How to suppress static initializers using PowerMock

In my previous post we saw how we could test methods of class whose constructor does evil stuff.  What if the static initializer of the class does evil stuff?  How will we test such a class?
This is exactly what we are going to see in this post!

You might think why on earth would you want to do that? For a few very simple reasons like, legacy code, third party library, may be something else.

At times, we might have to use a third party library that does some weired stuff in static initializers. It might load a native library, may perform some IO operation, may open up a database connection etc.
When we are unit-testing a class that uses this third party class (whose static initializer does wired stuff), we simply don't want to deal with all this complication.

This is the reason why we might want to suppress the static initializer of a class.

Convinced about the "why"? Lets look at the "What" part.

Code under test:

Hmmm, looks like a familiar class?
We want to write a unit-test case for the AccountDAO class. In that we are interested in testing the findAccountById method. This method get a Hibernate session from HibernateUtils class and then invokes the get method on the session.

But look at how HibernateUtil class.  It initializes the static SessionFactory instance.  It does that in a static initializer!  Man! this means every time the unit-test case for AccountDAO is executed the sessionFactory will be created. We don't want that to happen do we?

Also notice that, for testing purpose I have a System.out.println line in the static initializer of HibernateUtil. If we are successfully able to suppress the static initializer of HibernateUtil we should not be getting any output on the console.

So how do we write unit-test for AccountDAO class without initializing the SessionFactory.  Lets look at the "How" part now.

How would we test it - How do they do it!

Turns out, as always, there is a very simple way to suppress the static initializers of any class using PowerMocks - Mocks on Steroids! 
We simply have to use an annotation called @SuppressStaticInitializationFor.  As the annotation reads it suppresses static initializer for a given class.  But which classes static initializer do we want to suppress?  How do we tell this to PowerMocks? Via passing the fully qualified class name to the @SuppressStaticInitializationFor annotation.

Here is the syntax

Please note that we are passing the fully qualified class name as string to the @SuppressStaticInitializationFor annotation.  Unlike in the case of @PrepareForTest annotation where, we pass the class reference like this

Can you tell me why this difference in the syntax? Well, for those who have not guess it, here's the answer:
Because if we pass the class reference like HibernateUtil.class then, by the time that statement executes the class is already loaded and static initializer would have already been executed. We don't want that.
Sole purpose of using the @SuppressStaticInitializationFor annotation was to suppress the static initializer of HibernateUtil class. And this is the reason we don't pass the class reference but we pass the fully qualified class name as string.
After a lot of explanation lets look at the complete test code

Thats it! We have managed to suppress the static initializer of a class.
Writing unit-tests with PowerMocks (Mockito api) is fun!
BTW the statement "The static initializer of HibernateUtil invoked" is never printed on the console. You will have to take my word for this! What? You don't trust me? Then, try the example on your own :)

1 comment:

  1. Hello Deep !

    It's a reealy great job you have done here with your PowerMock tutorials. Thank you ! I'm learning with it theses "how to" since a week.

    well, I have a little problem still. I hope you got an answer :

    How to mock an array of a final Class ?
    like File[].

    (my working environment : win xp, eclipse indigo, mock 1.8.5, powermock 1.4.10)
    classpath and import looks ok.

    let me present you my example :
    CODE UNDER TEST :

    public class ClassToTest {
    protected File[] listOfThings;
    protected int numberOfThings;

    protected int calculation(File[] arg_listOfSomething) {
    return arg_listOfSomething.length;
    }
    }

    TEST CODE :
    @RunWith(PowerMockRunner.class)
    @PrepareForTest(value = {File[].class})
    public class ClassToTestTest {

    ClassToTest c = new ClassToTest();

    @Test
    public void testcalculation() {
    // without PowerMockito : error : Mockito cannot mock final classes.
    c.listOfThings = PowerMockito.mock(File[].class);
    when(c.listOfThings.length).thenReturn(3);

    assertEquals(3, c.calculation(c.listOfThings));
    }
    }

    with this code, I got this error java.lang.IllegalArgumentException: Cannot subclass final class class [Ljava.io.File;


    Would be very helpfull to share the solution. Thank you again !

    ReplyDelete

Have some Fun!