Over a million developers have joined DZone.

On PowerMock Abuse

A tutorial on how to properly use PowerMock in the planning stages of development.

· Agile Zone

Reduce testing time & get feedback faster through automation. Read the Benefits of Parallel Testing, brought to you in partnership with Sauce Labs.

PowerMock logo

Still working on my legacy application, and still trying to improve unit tests.

This week, I noticed how much PowerMock was used throughout the tests, to mock either static or private methods. In one specific package, removing it improved tests execution time by one order of magnitude (from around 20 seconds to 2). That’s clearly abuse: I saw three main reasons of using PowerMock.

Lack of Knowledge of the API

There probably must have been good reasons, but some of PowerMock uses could have been avoided if developers had just checked the underlying code. One example of such code was the following:

@RunWith(PowerMockRunner.class)
@PrepareForTest(SecurityContextHolder.class)
public class ExampleTest {

    @Mock private SecurityContext securityContext;

    public void setUp() throws Exception {
        mockStatic(SecurityContextHolder.class);
        when(SecurityContextHolder.getContext()).thenReturn(securityContext);
    }

    // Rest of the test
}

Just a quick glance at Spring’s SecurityContextHolder reveals it has a setContext() method, so that the previous snippet can easily be replaced with:

@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {

    @Mock private SecurityContext securityContext;

    public void setUp() throws Exception {
        SecurityContextHolder.setContext(securityContext);
    }

    // Rest of the test
}

Another common snippet I noticed was the following:

@RunWith(PowerMockRunner.class)
@PrepareForTest(WebApplicationContextUtils.class)
public class ExampleTest {

    @Mock private WebApplicationContext wac;

    public void setUp() throws Exception {
        mockStatic(WebApplicationContextUtils.class);
        when(WebApplicationContextUtils.getWebApplicationContext(any(ServletContext.class))).thenReturn(wac);
    }

    // Rest of the test
}

While slightly harder than the previous example, looking at the source code of WebApplicationContextUtils reveals it looks into the servlet context for the context.

The testing code can be easily change to remove PowerMock:

@RunWith(MockitoJUnitRunner.class)
public class ExampleTest {

    @Mock private WebApplicationContext wac;
    @Mock private ServletContext sc;

    public void setUp() throws Exception {
        when(sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)).thenReturn(wac);
    }

    // Rest of the test
}

Too Strict Visibility

As seen above, good frameworks — such as Spring, make it easy to use them in tests. Unfortunately, the same cannot always be said of our code. In this case, I removed PowerMock by widening the visibility of methods and classes from private (or package) to public.

You could argue that breaking encapsulation to improve tests is wrong, but in this case, I tend to agree with Uncle Bob:

Tests trump Encapsulation.


In fact, you think your encapsulation prevents other developers from misusing your code. Yet, you break it with reflection within your tests. What guarantees developers won’t use reflection the same way in production code?

A pragmatic solution is to compromise your design a bit but — and that’s the heart of the matter, document it. Guava and Fest libraries have both a @VisibleForTesting annotation that I find quite convenient. Icing on the cake would be for IDEs to recognize it and not propose auto-completion in src/main/java folder.

Direct Usage of Static Methods

This last point has been explained times and times again, but some developers still fail to apply it correctly. Some very common APIs offer only static methods and they have no alternatives e.g. Locale.getDefault() or Calendar.getInstance(). Such methods shouldn’t be called directly on your production code, or they’ll make your design testable only with PowerMock.

public class UntestableFoo {

    public void doStuff() {
        Calendar cal = Calendar.getInstance();
        // Do stuff on calendar;
    }
}

@RunWith(PowerMock.class)
@PrepareForTest(Calendar.class)
public class UntestableFooTest {

    @Mock
    private Calendar cal;

    private UntestableFoo foo;

    @Before
    public void setUp() {
        mockStatic(Calendar.class);
        when(Calendar.getInstance()).thenReturn(cal);
        // Stub cal accordingly
        foo = new UntestableFoo();
    }

    // Add relevant test methods
}

To fix this design flaw, simply use injection and more precisely constructor injection:

public class TestableFoo {

    private final Calendar calendar;

    public TestableFoo(Calendar calendar) {
        this.calendar = calendar;
    }

    public void doStuff() {
        // Do stuff on calendar;
    }
}

@RunWith(MockitoJUnitRunner.class)
public class TestableFooTest {

    @Mock
    private Calendar cal;

    private TestableFoo foo;

    @Before
    public void setUp() {
        // Stub cal accordingly
        foo = new TestableFoo(cal);
    }

    // Add relevant test methods
}

At this point, the only question is how to create the instance in the first place. Quite easily, depending on your injection method: Spring @Bean methods, CDI @Inject Provider methods or calling the getInstance() method in one of your own. Here’s the Spring way:

@Configuration
public class MyConfiguration {

    @Bean
    public Calendar calendar() {
        return Calendar.getInstance();
    }

    @Bean
    public TestableFoo foo() {
        return new TestableFoo(calendar());
    }
}

Conclusion

PowerMock is a very powerful and useful tool. But it should only be used when it’s strictly necessary as it has a huge impact on test execution time. In this article, I’ve tried to show how you can do without it in 3 different use-cases: lack of knowledge of the API, too strict visibility and direct static method calls. If you notice your test codebase full of PowerMock usages, I suggest you try the aforementioned techniques to get rid of them.

Note: I’ve never been a fan of TDD (probably the subject of another article) but I believe the last 2 points could easily have been avoided if TDD would have been used.

The Agile Zone is brought to you in partnership with Sauce Labs. Discover how to optimize your DevOps workflows with our cloud-based automated testing infrastructure.

Topics:
mock ,unit test ,performance

Published at DZone with permission of Nicolas Frankel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}