15

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?

Colin Harrington
  • 4,459
  • 2
  • 27
  • 32
  • Can you change your SomeServiceImpl implementation? – Samuel Carrijo Jul 29 '09 at 21:07
  • Never mind, Jon Skeet just posted what I thought. I feel proud! (thinking like Jon Skeet hehe) – Samuel Carrijo Jul 29 '09 at 21:09
  • Yes I can change SomeServiceImpl, but why should I have to? Why the extra indirection? – Colin Harrington Jul 29 '09 at 22:11
  • 1
    By doing "magic" to break the dependency on the static call, you're invalidating the point of using dependency injection. The idea is to make dependencies explicit so that you can manage them. Some argue that the whole point of DI engines like Spring is to manage Singletons. – Steve Freeman Sep 05 '09 at 20:24

8 Answers8

14

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
  • 1,421,763
  • 867
  • 9,128
  • 9,194
4

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
  • 398,947
  • 96
  • 818
  • 769
  • 1
    The use of static methods is always going to be controversial. But can anyone really argue against judicious use of the final keyword? Consider, for example, what the "Effective Java" book has to say about it (item 17). – Rogério Aug 02 '09 at 17:01
  • 1
    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 Aug 04 '09 at 03:41
  • So JMockit is out of the question for now. – Colin Harrington Aug 04 '09 at 03:41
4

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
  • 6,682
  • 1
  • 26
  • 35
1
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
  • 39,912
  • 17
  • 102
  • 155
1

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.

Rogério
  • 16,171
  • 2
  • 50
  • 63
0

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
  • 62,186
  • 18
  • 105
  • 157
0

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

Gaël Marziou
  • 16,028
  • 4
  • 38
  • 49
0

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
  • 4,459
  • 2
  • 27
  • 32
  • 2
    PowerMock has been suggested and that is able to mock static methods, so there is an easy way – Richard Mar 24 '11 at 16:27
  • PowerMock has serious memory leaks, which is what we discovered after seeing the memory usage and duration of our Jenkins builds when we used a maven plugin, which would automatically execute unit tests to ensure build quality. This is why PowerMock is a bad option. – Igor Jan 08 '14 at 17:54
  • Check this out, https://blog.jayway.com/2014/11/29/using-another-junit-runner-with-powermock/ You can @RunWith(PowerMockRunner.class) and then delegate to @PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class). Of course this will require you to work with some latest versions. – Yasin Jan 18 '17 at 06:24