java - 在类的私有静态最终字段中模拟静态方法调用
问题描述
我有一个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()
如果我删除常量字段中的方法调用(并因此删除对它的期望)测试成功。
问题是什么?
解决方案
一般评论
请在其余答案之前阅读此哲学评论。
我经常遇到可测试性差的代码。我们有 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")
执行的,并且 PowerMock 正确地抱怨缺少行为定义。 expect(Utils.getMessage("btn-key")).andReturn("btn-key : message");
可能的快速解决方案
如果你想保持你的静态逻辑,有一个快速的解决方法。不要在静态初始化程序块中启动您的 BUTTON_KEY,而是懒惰地启动您的 BUTTON_KEY。
我不太喜欢它,我还是更喜欢完全摆脱静态调用。
我在代码中留下了 test 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`)
推荐阅读
- character-encoding - 计算机的字符编码
- powerbi - 如何从 Power Query 中的一个日期列创建开始日期和结束日期列?
- azure-devops - Azure DevOps 触发管道
- python - 如何从产生两个二维数组输出的函数的 1 个输出中选择 1 行数据
- sql - 在特定日期和时间之后获取数据,但日期和时间在 SQL Server 的不同列中
- php - Laravel 过滤一对多关系
- python - 在一列的每一行中搜索特定字符
- c++ - 具有不同类型算术的嵌套可变参数模板参数的构造函数
- google-sheets - Google 表格:在 Arrayformula 函数中重复 INDEX 函数
- vmware - 服务条款——或者我可以在我的网站上分发“免费”软件的副本吗?