如何在Java(jUnit4)中轻松模拟静态方法

如何在Java(jUnit4)中轻松模拟静态方法,java,spring,static,mocking,Java,Spring,Static,Mocking,如何在Java中轻松模拟静态方法 我正在使用Spring2.5和JUnit4.4 @Service public class SomeServiceImpl implements SomeService { public Object doSomething() { Logger.getLogger(this.class); //a static method invoked. // ... } } 我不控制我的服务需要调用的静态方法,因此我无

如何在Java中轻松模拟静态方法

我正在使用Spring2.5和JUnit4.4

@Service
public class SomeServiceImpl implements SomeService {

    public Object doSomething() {
        Logger.getLogger(this.class); //a static method invoked.
        // ...
    }
}
我不控制我的服务需要调用的静态方法,因此我无法将其重构为更易于单元测试的方法。我已经使用了作为一个例子,但真正的静态方法是类似的。更改静态方法不是一个选项

在Grails工作时,我习惯于使用以下内容:

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

我如何在Java中执行类似的操作?

您的意思是不能控制调用代码吗?因为如果您控制对静态方法的调用,而不是实现本身,则可以轻松地使其可测试。使用与静态方法具有相同签名的单个方法创建依赖项接口。您的生产实现将只调用静态方法,但是当前调用静态方法的任何东西都将通过接口调用

然后,您可以以正常方式模拟该接口。

public interface LoggerWrapper{
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;}
    }
公共记录器(c类); } 公共类RealLoggerWrapper实现LoggerWrapper{ 公共记录器getLogger(类c){return Logger.getLogger(c);} } 公共类MockLoggerWrapper实现LoggerWrapper{ 公共记录器getLogger(类c){returnsomethingelse;} }
这就是静态方法不好的原因之一

我们对大多数工厂进行了重新设计,使其也具有setter,以便可以将模拟对象设置到其中。事实上,我们提出了一些类似于依赖注入的方法,其中一个方法充当所有单例的工厂


在您的情况下,添加Logger.setLogger()方法(并存储该值)可能有效。如果必须的话,您可以扩展logger类并使用自己的方法来隐藏getLogger方法。

JMockit框架承诺允许模拟静态方法

事实上,它提出了一些相当大胆的主张,包括静态方法是一种完全有效的设计选择,它们的使用不应该因为测试框架的不足而受到限制


不管这种说法是否合理,JMockit框架本身是非常有趣的,尽管我自己还没有尝试过

您可以使用AspectJ拦截静态方法调用,并为您的测试做一些有用的事情。

如上所述,您可以模拟静态方法(以及其他任何方法)

它甚至直接支持日志框架。例如,您可以编写:


@UsingMocksAndStubs(Log4jMocks.class)
public class SomeServiceTest
{
    // All test methods in this class will have any calls
    // to the Log4J API automatically stubbed out.
}
但是,对JUnit4.4的支持被放弃了。JUnit 3.8、JUnit 4.5+和TestNG 5.8+均受支持。

具有此功能。它还可以模拟被测试类内对象的实例化。如果被测试的方法调用new Foo(),则可以为该Foo创建一个模拟对象,并将其替换到正在测试的方法中


抑制构造函数和静态初始值设定项也是可能的。所有这些都被认为是不稳定的代码,因此不建议您这样做,但如果您有遗留代码,更改它并不总是一个选项。如果您处于该位置,我们可以帮助您。

基本上,目前在Java+Spring2.5和JUnit4.4中没有一种简单的方法可以做到这一点

虽然可以重构并抽象掉静态调用,但重构代码并不是我所寻找的解决方案


JMockit看起来可以工作,但与Spring 2.5和JUnit 4.4不兼容。

您能更改SomeServiceImpl实现吗?没关系,Jon Skeet刚刚发布了我的想法。我感到骄傲!(像Jon Skeet hehe一样思考)是的,我可以改变一些简单的服务,但为什么我必须改变?为什么会有额外的间接性?通过使用“魔法”来打破对静态调用的依赖,您就失去了使用依赖注入的意义。其思想是使依赖项显式化,以便您可以管理它们。一些人认为,像Spring这样的DI引擎的全部目的是管理单例。静态方法的使用总是有争议的。但有人真的能反对明智地使用
final
关键字吗?例如,考虑“有效java”书所要说的(第17项)。当我使用JMOCKIT时,我发现Spring 2.5。x与JUnit 4.5 +不兼容,JMOCKit与JUnit 4.4(及以下)兼容。通过VISK,JMockit现在是不可能的。我怎么做?有一个简单的例子吗?有人建议PowerMock能够模拟静态方法,因此PowerMock有一个简单的方法存在严重的内存泄漏,这是我们在使用maven插件时看到Jenkins构建的内存使用情况和持续时间后发现的,该插件将自动执行单元测试以确保构建质量。这就是为什么PowerMock是一个不好的选项。请检查这一点,您可以@RunWith(PowerMockRunner.class)然后委托给@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)。当然,这需要您使用一些最新版本。