如何在Java中轻松模拟静态方法?
我正在使用Spring 2.5和JUnit 4.4
@Service
public class SomeServiceImpl implements SomeService {
public Object doSomething() {
Logger.getLogger(this.class); //a static method invoked.
// ...
}
}
我不控制我的服务需要调用的静态方法,所以我不能重构它以使其更易于单元测试。我使用Log4J Logger作为示例,但真正的静态方法是类似的。 不能更改静态方法。
做Grails工作,我习惯使用类似的东西:
def mockedControl = mockFor(Logger)
mockControl.demand.static.getLogger{Class clazz-> … }
…
mockControl.verify()
我如何在Java中做类似的事情?
答案 0 :(得分:14)
你的意思是你无法控制主叫代码吗?因为如果您控制调用到静态方法而不是实现本身,您可以轻松地使其可测试。使用与静态方法具有相同签名的单个方法创建依赖关系接口。您的生产实现只会调用静态方法,但是当前调用静态方法的任何内容都将通过接口调用。
然后你可以用正常方式模拟出那个界面。
答案 1 :(得分:4)
JMockit框架承诺允许模拟静态方法。
事实上,它提出了一些相当大胆的主张,包括静态方法是一种完全有效的设计选择,并且由于测试框架的不足,它们的使用不应受到限制。
无论这些声明是否合理,JMockit框架本身都非常有趣,尽管我还没有尝试过。
答案 2 :(得分:4)
PowerMock具备此功能。它还可以模拟测试中的对象内部的实例化。如果您测试的方法调用了新的Foo(),您可以为该Foo创建一个模拟对象,并在您正在测试的方法中替换它。
抑制构造函数和静态初始化程序之类的东西也是可能的。所有这些都被认为是不可测试的代码,因此不建议这样做,但如果您有遗留代码,更改它并不总是一个选项。如果您处于该职位,PowerMock可以帮助您。
答案 3 :(得分: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;}
}
答案 4 :(得分:1)
正如上面提到的另一个答案,JMockit可以模拟静态方法(以及其他任何东西)。
它甚至直接支持日志记录框架。例如,您可以写:
@UsingMocksAndStubs(Log4jMocks.class)
public class SomeServiceTest
{
// All test methods in this class will have any calls
// to the Log4J API automatically stubbed out.
}
然而,对JUnit 4.4的支持被删除了。支持JUnit 3.8,JUnit 4.5+和TestNG 5.8+。
答案 5 :(得分:0)
这是静态方法不好的原因之一。
我们为大多数工厂重新设计了setter,以便我们可以将mock对象设置到它们中。实际上,我们提出了一些接近依赖注入的东西,其中一个方法充当了我们所有单身人士的工厂。
在您的情况下,添加Logger.setLogger()方法(并存储该值)可能会起作用。如果必须,您可以扩展记录器类并使用您自己的方法遮蔽getLogger方法。
答案 6 :(得分:0)
您可以使用AspectJ来拦截静态方法调用,并为您的测试做一些有用的事情。
答案 7 :(得分:0)
基本上,在Java + Spring 2.5&amp ;;中没有一种简单的方法可以做到这一点。 JUnit 4.4目前。
虽然可以重构和抽象掉静态调用,但重构代码并不是我想要的解决方案。
JMockit看起来会起作用,但与Spring 2.5和JUnit 4.4不兼容。