类的私有静态最终字段中的模拟静态方法调用

时间:2018-06-30 07:27:32

标签: java junit powermock easymock

我有一个基本的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()

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

出什么问题了?

1 个答案:

答案 0 :(得分:2)

一般评论

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

我经常遇到可测试性很差的代码。我们有Spring,有依赖注入,为什么我们仍然被敦促使用静态方法和实用程序类?

当您想使用PowerMock时,请首先考虑对代码进行少量重构是否会有所帮助。

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

另一方面,有些乍一看似乎是“静态”的方法在您开始考虑测试时就出卖了您,例如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(),因此您可以看到调用的顺序。

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`)