【问题标题】:mock static method call in private static final field of the class在类的私有静态最终字段中模拟静态方法调用
【发布时间】:2018-06-30 07:27:32
【问题描述】:

我有一个基本的Widget 类,它使用实用程序类Utils

public class Widget {

    private static final String BUTTON_KEY = Utils.getMessage("btn-key");

    public boolean comp() {
        String specialKey = Utils.getMessage("special-key");
        return specialKey.equals(BUTTON_KEY);
    }

}

class Utils {

    public static String getMessage(String key) {
        return key + " : message";
    }

}

我想为comp 方法创建一个测试。

我的测试是这样的

@RunWith(PowerMockRunner.class)
@PrepareForTest(Utils.class)
public class WidgetTest {

    private Widget widget;

    @Before
    public void setUp() {
        mockStatic(Utils.class);
        widget = new Widget();
    }

    @Test
    public void testComp() {
        expect(Utils.getMessage("btn-key")).andReturn("btn-key : message");
        expect(Utils.getMessage("special-key")).andReturn("special-key : message");
        replayAll();
        assertFalse(widget.comp());
        verifyAll();
    }

}

测试失败

java.lang.IllegalStateException: missing behavior definition for the preceding method call:
Utils.getMessage("btn-key")
Usage is: expect(a.foo()).andXXX()

如果我删除常量字段中的方法调用(并因此删除对它的期望)测试成功。

有什么问题?

【问题讨论】:

  • 我不敢说,但由于BUTTON_KEY 的静态初始化,您的Widget 类不可测试。

标签: java junit powermock easymock


【解决方案1】:

一般评论

请在其余答案之前阅读此哲学评论。

我经常遇到可测试性差的代码。我们有 Spring,我们有 Dependency Injection,为什么我们仍然觉得需要使用静态方法和实用程序类?

当您想使用 PowerMock 时,请首先考虑对您的代码进行小的重构是否对您没有更好的帮助。

您需要模拟您的方法这一纯粹事实意味着该方法并不是真正的静态。好吧,也许Math.sqrt()Assert.assertEquals() 是真正静态 方法的好例子。

另一方面,一些乍一看可能看起来“静态”的方法会在您开始考虑测试时背叛您,例如LocalDate.now()。在您的测试中,您需要静态的是当前日期,而不是方法:)

这里有很多好主意:https://softwareengineering.stackexchange.com/q/148049/105827


你的问题

问题在于Widget 类的静态初始化程序在您开始定义期望之前调用

如果将此代码添加到 Widget 类的任何位置,您可能会看到:

static {
    System.out.println("Widget.static");
}

这段代码以WidgetTest.testComp()方法开头:

public void testComp() throws Exception {
    System.out.println("test method");
    ... // the rest of the method
}

当你运行测试时,输出是这样的:

Widget.static
test method

这意味着 BUTTON_KEY = Utils.getMessage("btn-key") 被执行之前 expect(Utils.getMessage("btn-key")).andReturn("btn-key : message"); 并且 PowerMock 正确地抱怨缺少行为定义。


可能的快速解决方案

如果您想保留静态逻辑,有一个快速的解决方法。不要在静态初始化程序块中初始化 BUTTON_KEY,而是懒惰地初始化。

我不是很喜欢,我还是更喜欢完全摆脱静态调用。

我在代码中留下了测试println()s,所以你可以看到调用的顺序。

public class Widget {
    private static String BUTTON_KEY;

    static {
        System.out.println("Widget.static");
    }

    public boolean comp() {
        String specialKey = Utils.getMessage("special-key");
        return specialKey.equals(getButtonKey());
    }

    private static String getButtonKey() {
        synchronized (Widget.class) {
            if (BUTTON_KEY == null) {
                System.out.println("Widget is calling Utils.getMessage(`btn-key`)");
                BUTTON_KEY = Utils.getMessage("btn-key");
            }
        }
        return BUTTON_KEY;
    }
}

class Utils {
    public static String getMessage(String key) {
        return key + " : message";
    }
}

调用顺序是这样的:

Widget.static
test method
Widget is calling Utils.getMessage(`btn-key`)

【讨论】:

  • 该方法是静态的,因为它是实用程序类的一部分,允许从.properties 文件中获取消息。所以基本上我必须删除常量或其他东西......
  • @lapots 查看我的编辑 - 我提供了如何将 Util 类变成可测试的东西的链接。或者只是搜索谷歌:Java how to turn static utility class into testable class。最好花时间修复您的课程,以便 无需 PowerMock 即可测试,而不是尝试解决问题。 PowerMock 本身就是一种解决方法 :)
  • 问题是实用程序类不是我的,而是外部依赖。最多可以做的是将初始化移动到方法内的一些变量
  • “您需要模拟您的方法这一纯粹事实意味着该方法并不是真正的静态”。说到点子上了。 +1。
  • @ShanuGupta 在某些类中我确实将它模拟为静态并按原样模拟它。但我无能为力。只有解决方法
猜你喜欢
  • 1970-01-01
  • 2012-09-29
  • 1970-01-01
  • 2015-08-22
  • 1970-01-01
  • 2017-10-08
  • 1970-01-01
  • 2013-07-14
  • 1970-01-01
相关资源
最近更新 更多