| How to Mock Static Methods |
|
|
Using PowerMockLet's start off with the basics. To use PowerMock, you'll have to download it from Google Code here: http://code.google.com/p/powermock/ (or you can add it to your POM if you are using maven). Once PowerMock is added to your project, mocking static methods is actually very easy. Mocking a Static MethodAs in all of my previous tutorials, we're going to do this one via TDD (Test Driven Development). For this example, we are going to assume we have the following utility class:
import java.net.InetAddress;
import java.net.UnknownHostException;
public final class NetworkUtil {
public static String getLocalHostname() {
String hostname = "";
try {
InetAddress addr = InetAddress.getLocalHost();
// Get hostname
hostname = addr.getHostName();
} catch ( UnknownHostException e ) {
}
return hostname;
}
}
As you can see, it returns the human readable version of the machine name. Now there is an issue with this. Chances are that the above code would run fine if we let it run through a unit test regardless of what environment we are in (*nix, Windows, OS X, etc). The issue is, however, we cannot predict the output. One of the key aspects of unit testing is the ability to predict the output of our test based on a given input. In this case, as we move our test from environment to environment, the results of the above method will change. Enter PowerMock. So the requirements for our use of the above utility class is to create a URL to an image hosted on the local machine. The final URL should be in the following format: http://LOCAL_MACHINE_NAME/myapplication/images/myimage.gif Now you can stop laughing at the example. The purpose of this tutorial is not to find some painful testing feature, it's to show where the ability to mock static method calls is useful. So let's start our test:
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
@RunWith( PowerMockRunner.class )
@PrepareForTest( NetworkUtil.class )
public class URLGeneratorTest {
}
You'll notice immediately that there are two annotations needed to use PowerMock. First, we don't use the default JUnit runner when we use PowerMock. PowerMock provides it's own runner which we will need to use, specified with the The creation of our setUp and shell test methods should look normal. Nothing exciting here:
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import static org.powermock.api.easymock.PowerMock.mockStatic;
import static org.powermock.api.easymock.PowerMock.replayAll;
import static org.powermock.api.easymock.PowerMock.verifyAll;
import static org.easymock.EasyMock.expect;
@RunWith( PowerMockRunner.class )
@PrepareForTest( NetworkUtil.class )
public class URLGeneratorTest {
private URLGenerator generator;
@Before
public void setUp() {
generator = new URLGenerator();
}
@Test
public void testGenerateURL() {
}
}
Now let's focus specifically on the test method. We're essentially going to do XYZ things:
@Test
public void testGenerateURL() {
mockStatic( NetworkUtil.class );
expect( NetworkUtil.getLocalHostname() ).andReturn( "localhost" );
replayAll();
String results = generator.generateURL();
verifyAll();
assertEquals(
"http://localhost/myapplication/images/myimage.gif",
results );
}
That's it. The above code is all you need to test those "nasty" static methods. The untestable is now testable. Oh, wait. I guess we should write the actual implementing code so we can prove all of this works. Ok, check out the method that implements this test below:
public class URLGenerator {
public String generateURL() {
return "http://" +
NetworkUtil.getLocalHostname() +
"/myapplication/images/myimage.gif";
}
}
I never said it would be pretty. But what it does do is it implements the test correctly and demonstrates my point. ConclusionSo that's it. Mocking static methods (and other "untestable code") is actually quite easy when you put PowerMock to work. One thing I want to point out that PowerMock helped me see. Just because you can't do something with your current toolset doesn't mean that it's bad. It just means you may need to re-evaluate your toolset. As always, I'm excited to read your comments! Have you used PowerMock? What about other ways of addressing the same issue? Let's hear 'em! 11 ResponsesAdd your own comment... |
| Next > |
|---|
This is why I don't like powermock. It screws with the class loader and using pertest is NOT a good option.
http://code.google.com/p/powermock/issues/detail?id=122&q=pertest
Static methods can also be a smell because when you reference them statically (as you are supposed to), it's hard coding the implementation which gets in the way of IoC and Dependency Injection techniques.
Do I still use static methods? Yes, but very rarely. Powermock is great for testing legacy code or stuff you don't have control over, but I think it doesn't force one to think about design the same way that standard Mock APIs do. In short, it's too powerful (http://skjutare.eu/?p=22#comments) It should be used only when absolutely necessary, not by default.
Since you asked about "other ways"... ;^)
Here is the JMockit version of the test:
@Test
public void testGenerateURL()
{
new NonStrictExpectations()
{
NetworkUtil mock;
{
NetworkUtil.getLocalHostname(); result = "localhost";
}
};
String results = generator.generateURL();
assertEquals("http://localhost/myapplication/images/myimage.gif", results);
}
A JMockit test class doesn't need to be annotated at all (not even @RunWith), nor extend a base class. (Also, "-javaagent" is easily avoided.)
The same test can also be written as:
@Test
public void testGenerateURL_mockingJREClasses() throws Exception
{
new NonStrictExpectations()
{
@Cascading InetAddress mock;
{
mock.getHostName(); result = "localhost";
}
};
String results = generator.generateURL();
assertEquals("http://localhost/myapplication/images/myimage.gif", results);
}
Joe,
I agree with you in regards to evaluating the use of static methods, however they are a tool just like any other feature of the Java language. They have their place and shouldn't be avoided just because EasyMock can't handle them.
Rogerio,
I've blogged about JMockit before and have a tutorial on this site about it. It's a great framework. Thanks for the great example!
Intresting solution. Thanks
When your test frameworks don't play nicely with implementations that use static methods, I don't think that you should adopt frameworks that use complex class-loader manipulations so deterministically.
I have detailed my response here: http://christianposta.com/blog/?p=56
Very informative post. Thanks for taking the time to share your view with us.
How do I test static methods that do not return anything?
To clarify, how do I mock a DEPENDENT class that has a static method with void return type? So in other words if NetworkUtil.getLocalHostname() did not return anything - how can I mock that?
Man, I couldn't agree with this more. I'm so tired of listening to people try to bend the rules of good design around their tools. There are plenty of cases where using static methods is a good clean design. Of course they can be abused, but so can anything. The point of good design is making the right choices for the circumstances not mantras like "x is always bad".
Great solution. Thanks!
Anything can be abused, but some things are easier to do so than others. Static methods are procedural, not OO code. This is hard-wiring implementation, not thinking in an abstract/object way.
Rules of good design don't mention static methods in OO paradigm. Neither they mention recursive calls, and both for a reason.
Yes, you can write good procedural or functional code in OO language, like Java. Yes, these things are possible. It's also possible to test them - fully agree here.
Yet this doesn't mean you should. Neither does mentioning Gosling's team putting them there. They put in checked exceptions and they are flawed too. Finally, manipulating B-code to test things... that's big. Yes, possible. But... for me it triggers a warning alert.
With all that said - detailed and informative post, for which I thank the author.
Especially since parts about "static = testable" aren't that bountiful to become hard to read and yes, there will be cases I won't have any other options than test static methods. They are part of Java so will be used.