Java:如何模拟Calendar.getInstance()?

34

我的代码中有类似这样的内容:

private void doSomething() {
   Calendar today = Calendar.getInstance();
   ....
}

如何在我的JUnit测试中“模拟”它以返回特定日期?


1
那行代码无法编译! - adarshr
today是一个类成员(字段)还是方法内的局部变量? - Andreas Dolk
方法内的局部变量 - Randomize
1
同一个问题的现代版本(带答案):使用Java 8日期/时间类编写和测试便捷方法 - Ole V.V.
1
更新...顺便提一下,糟糕的“Calendar”类早在多年前就被JSR 310中定义的现代* java.time *类所取代。 - Basil Bourque
你可以使用java.time.Clock类上的static方法来伪造一个时钟。将伪造的Clock实例传递给各种java.time.*类上的不同方法。 - undefined
9个回答

29

您可以使用PowerMock与Mockito结合来模拟它:

在您的类顶部:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassThatCallsTheCalendar.class})
成功的关键在于,在使用Calendar类时,需要将它放到PrepareForTest中而不是直接使用Calendar本身,因为它是一个系统类。(我个人在找到这个方法之前搜索了很多次)
然后是模拟本身:
mockStatic(Calendar.class);
when(Calendar.getInstance()).thenReturn(calendar);

1
你需要包含哪些依赖项才能使mockStatic()方法工作? - Leo DroidCoder
抱歉,我应该说一下。我使用了import static来导入PowerMockito的mockStatic方法。请参考此依赖项以获取powermock mockito:http://mvnrepository.com/artifact/org.powermock/powermock-api-mockito2/1.6.6 - GoGoris
你让我的一天美好了! - Anand Vaidya
+1,但是对于第二部分,两行代码可以简化为:PowerMockito.whenNew(Calendar.getInstance()).withAnyArguments().thenReturn(MY_INSTANCE_OBJECT_TO_FEED); - Payam
@GoGoris,您介意更新答案以显示这些注释和PowerMockito调用在测试类中的确切位置吗? - lasec0203

18
据我看,你有三个明智的选择:
  1. Calendar实例注入到你设置那天的任何方法/类中。

    private void method(final Calendar cal) { Date today = cal.getTime(); }

  2. 使用JodaTime代替Calendar。这不仅是一个选项,更像是一种建议,因为JodaTime会让你的生活变得更轻松。你仍然需要将这个时间注入到该方法中。

    DateTime dt = new DateTime();

    Date jdkDate = dt.toDate();

  3. 在某个接口内包装Calendar,以允许您获取时间。然后你只需要模拟这个接口并让它返回一个常量的Date

    Date today = calendarInterfaceInstance.getCurrentDate()


4
Joda Time 的 DateTimeUtils 类有一些静态方法,可以为所有其他 Joda Time 对象设置当前时间。这对于将时间设置为某个时刻(例如用于测试)非常有用。 - Jesper
@Jesper - 是的,这是真的,也是我忽略提到的一个好观点。 - BeRecursive
1
谢谢大家。我按照你们的建议,转向使用JodaTime。顺便说一句,它可以轻松解决这样的问题:DateTimeUtils.setCurrentMillisFixed(new DateTime(2012, 2, 14, 13, 43, 21).getMillis()); - Randomize

12

不要模拟它 - 而是引入一个可以模拟日期的方法。像这样:

interface Utility {

    Date getDate();
}

Utilities implements Utility {


    public Date getDate() {

        return Calendar.getInstance().getTime();
    }

}

然后你可以将这个内容注入到你的类中,或者使用一个带有一堆静态方法和接口load方法的helper类:

public class AppUtil {

    private static Utility util = new Utilities();

    public static void load(Utility newUtil) {

         this.util = newUtil;
    }

    public static Date getDate() {

        return util.getDate();
    }

}

然后在你的应用程序代码中:

private void doSomething() {
   Date today = AppUtil.getDate();
   ....
}
你可以在测试方法中加载一个模拟接口。
@Test
public void shouldDoSomethingUseful() {
     Utility mockUtility = // .. create mock here
     AppUtil.load(mockUtility);

     // .. set up your expectations

     // exercise the functionality
     classUnderTest.doSomethingViaAPI();

     // ... maybe assert something 

}

另请参阅应该只模拟您拥有的类型吗?测试异味-一切都被模拟


6
使用Mockito和PowerMockito:
Calendar endOfMarch = Calendar.getInstance();
endOfMarch.set(2011, Calendar.MARCH, 27);
PowerMockito.mockStatic(Calendar.class);
Mockito.when(Calendar.getInstance()).thenReturn(endOfMarch);

请参考链接获取完整的代码。

2

编写一个名为 DateHelper 的类,其中包含一个方法 getCalendar,该方法返回 Calendar.getInstance()。对正在测试的类进行重构,使其具有类型为 DateHelper 的成员变量,并具有注入该成员变量的构造函数。在测试中使用该构造函数,以注入 DateHelper 的模拟版本,其中 getCalendar 已被存根以返回某些已知日期。


1

1
使用Mockk

 private lateinit var calendar: Calendar

    @Before
    fun setup() {
        calendar = mockk(relaxed = true)
        mockkStatic(Calendar::class)
        every { Calendar.getInstance() } returns calendar
    }

    @Test
    fun `test date when date is less than 16 month is 0`() {
        every { calendar[Calendar.DAY_OF_MONTH] } returns 12
        every { calendar[Calendar.MONTH] } returns 0
        every { calendar[Calendar.YEAR] } returns 2023

      // assert here

    }

请参考链接获取mockk文档。

2
2023年,无论是你还是其他人,都不应该想要使用(因此也不想嘲笑)Calendar类。它是为了弥补Date的不足而进行的一次大部分失败的尝试,并且一直很难使用。随着近10年前发布的现代Java日期和时间API - java.time的发布,DateCalendar都已经过时了。java.time具有许多功能,其中包括提供一个由你控制的时钟给获取当前时间的方法,因此你根本不需要进行模拟。 - undefined
1
在2023年,无论是你还是其他人都不应该想要使用(因此也不想嘲笑)Calendar类。它是对Date的不足之处进行弥补的大部分失败尝试,而且一直很麻烦。随着近10年前发布的Java 日期和时间 API,java.time的发布,DateCalendar都已过时。Java.time具有许多功能,其中包括向获得当前时间的方法提供您控制的时钟的可能性,因此您根本不需要模拟。 - Ole V.V.
1
@OleV.V. 这是真的 :) 但有时候你不得不处理遗留代码和旧框架:我有一些使用 dates 和老旧的 Calendar 的 thymeleaf 模板 :'( - Sylvain
1
@OleV.V. 是的,没错 :) 但有时候你不得不处理遗留代码和遗留框架:我有一些使用dates的thymeleaf模板,所以还得用老旧的Calendar :'( - undefined

0
避免使用 Calendar.getInstance(),而是使用 Mockito 方法返回所需的内容。 例如:
@Test
fun italianLocale_returnsItalianFormatDate() {
    val calendar: Calendar = Mockito.mock(Calendar::class.java)
    Mockito.`when`(calendar.get(Calendar.DAY_OF_MONTH)).thenReturn(27)
    Mockito.`when`(calendar.get(Calendar.YEAR)).thenReturn(2023)
    Mockito.`when`(calendar.get(Calendar.MONTH)).thenReturn(1)
    val formatted = calendar.toReadableDate()
    assert(formatted == "27/01/2023")
}

在您的gradle文件中导入Mockito:

使用以下代码:

testImplementation ("org.mockito.kotlin:mockito-kotlin:x.x.x")

或者(如果您使用的是Groovy)
testImplementation "org.mockito.kotlin:mockito-kotlin:x.x.x"

0
你可以使用Junit 5和Mockito来实现这个。
@Test
public void doSomething() {
    listCalendarParams("now", Calendar.getInstance());
    Calendar fakedCalendar = new GregorianCalendar(2023, Calendar.OCTOBER, 15, 10, 15);
    listCalendarParams("mock", fakedCalendar);
    try (MockedStatic<Calendar> mockedStatic = Mockito.mockStatic(Calendar.class, Mockito.CALLS_REAL_METHODS)) {
        mockedStatic.when(Calendar::getInstance).thenReturn(fakedCalendar);
        //This or any method here using Calendar.getInstance() will be replaced by mock defined above
        Calendar calendar = Calendar.getInstance();
        listCalendarParams("evaluated", calendar);
        assertEquals(Calendar.OCTOBER, calendar.get(Calendar.MONTH));
        assertEquals(15, calendar.get(Calendar.DAY_OF_MONTH));
        assertEquals(10, calendar.get(Calendar.HOUR_OF_DAY));
        assertEquals(15, calendar.get(Calendar.MINUTE));
    }
}

private static void listCalendarParams(String nameOfCal, Calendar calendar) {
    System.out.println(nameOfCal + ":\t" +
            new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss")
                    .format(calendar.getTime()));
}3

输出:

now:        2023-11-27T14:00:48 //This will change anytime you run it.
mock:       2023-10-15T10:15:00
evaluated:  2023-10-15T10:15:00

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接