views:

1537

answers:

8

How do I easily mock out a static method in Java?

I'm using Spring 2.5 and JUnit 4.4

@Service
public class SomeServiceImpl implements SomeService {

    public Object doSomething() {
     Logger.getLogger(this.class); //a static method invoked.
     // ...
    }
}

I don't control the static method that my service needs to invoke so I cannot refactor it to be more unit-testable. I've used the Log4J Logger as an example, but the real static method is similar. It is not an option to change the static method.

Doing Grails work, I'm used to using something like:

def mockedControl = mockFor(Logger)
mockControl.demand.static.getLogger{Class clazz-> … }
…
mockControl.verify()

How do I do something similar in Java?

+7  A: 

Do you mean you can't control the calling code? Because if you control the calls to the static method but not the implementation itself, you can easily make that testable. Create a dependency interface with a single method with the same signature as the static method. Your production implementation will just call through to the static method, but anything which currently calls the static method will call via the interface instead.

You can then mock out that interface in the normal way.

Jon Skeet
A: 
public interface LoggerWrapper {
    public Logger getLogger(Class<?> c);
    }
public class RealLoggerWrapper implements LoggerWrapper {
    public Logger getLogger(Class<?> c) {return Logger.getLogger(c);}
    }
public class MockLoggerWrapper implements LoggerWrapper {
    public Logger getLogger(Class<?> c) {return somethingElse;}
    }
Carl Manaster
A: 

That's one of the reason static methods are bad.

We re-architect ed most of our factories to have setters as well so that we could set mock objects into them. In fact, we came up with something close to dependency injection where a single method acted as a factory for all our singletons.

In your case, adding a Logger.setLogger() method (and storing that value) could work. If you have to you could extend the logger class and shadow the getLogger method with your own as well.

Bill K
+2  A: 

The JMockit framework promises to allow mocking of static methods.

https://jmockit.dev.java.net/

In fact, it makes some fairly bold claims, including that static methods are a perfectly valid design choice and their use should not be restricted because of the inadequacy of testing frameworks.

Regardless of whether or not such claims are justifiable, the JMockit framework itself is pretty interesting, although I've yet to try it myself.

skaffman
The use of static methods is always going to be controversial. But can anyone really argue against judicious use of the <code>final</code> keyword? Consider, for example, what the "Effective Java" book has to say about it (item 17).
Rogerio
When I looked into Using JMockit, I found that Spring 2.5.x is incompatible with JUnit 4.5+, and that JMockit is compatible with JUnit 4.4 (and below) see http://jira.springframework.org/browse/SPR-5145 via http://stackoverflow.com/questions/693115/junit4-spring-2-5-asserts-throw-noclassdeffounderror
Colin Harrington
So JMockit is out of the question for now.
Colin Harrington
A: 

You could use AspectJ to intercept the static method call and do something useful for your test.

Gaël Marziou
How would I do that? Is there a simple Example?
Colin Harrington
A: 

As another answer above stated, JMockit can mock static methods (and anything else, as well).

It even has direct support for logging frameworks. For example, you could write:


@UsingMocksAndStubs(Log4jMocks.class)
public class SomeServiceTest
{
    // All test methods in this class will have any calls
    // to the Log4J API automatically stubbed out.
}

Support for JUnit 4.4 was dropped, however. JUnit 3.8, JUnit 4.5+ and TestNG 5.8+ are supported.

Rogerio
+1  A: 

PowerMock has this ability. It can also mock instantiations of objects inside the class under test. If your tested method calls new Foo(), you can create a mock object for that Foo and replace it in the method you are testing.

Things like suppressing constructors and static initializers are also possible. All of these things are considered untestable code and thus not recommended to do but if you have legacy code, changing it is not always an option. If you are in that position, PowerMock can help you.

Gerco Dries
A: 

Basically, There isn't an easy way to do this in Java + Spring 2.5 & JUnit 4.4 at the moment.

Although it is possible to refactor, and abstract away the static call, Refactoring the code isn't the solution that I was looking for.

JMockit looked like it would work, but is incompatibile with Spring 2.5 and JUnit 4.4.

Colin Harrington