LocalDateTime.of返回null

问题描述:

我想单元测试使用java.time.LocalDateTime的代码。我能够得到模拟工作,但是当我添加时间(无论是分钟还是天)时,我最终得到的值为nullLocalDateTime.of返回null

@RunWith(PowerMockRunner.class) 
@PrepareForTest({ LocalDateTime.class }) 
public class LocalDateTimeMockTest 
{ 
    @Test 
    public void shouldCorrectlyCalculateTimeout() 
    { 
     // arrange 
     PowerMockito.mockStatic(LocalDateTime.class); 
     LocalDateTime fixedPointInTime = LocalDateTime.of(2017, 9, 11, 21, 28, 47); 
     BDDMockito.given(LocalDateTime.now()).willReturn(fixedPointInTime); 

     // act 
     LocalDateTime fixedTomorrow = LocalDateTime.now().plusDays(1); //shouldn't this have a NPE? 

     // assert 
     Assert.assertTrue(LocalDateTime.now() == fixedPointInTime); //Edit - both are Null 
     Assert.assertNotNull(fixedTomorrow); //Test fails here 
     Assert.assertEquals(12, fixedTomorrow.getDayOfMonth()); 
    } 
} 

我明白(嗯,我想我这样做)是LocalDateTime是不可改变的,而且我认为我应该得到一个新的实例,而不是null值。

原来,这是.of方法,它给了我一个null值。为什么?

+0

您是否尝试运行代码没有嘲笑? powermockito很有可能造成这个问题。 – Ali

+1

并考虑重新考虑使用PowerMock(ito);-) – GhostCat

+0

当嘲笑一个Java“系统”类(如'java.time.LocalDateTime')时,PowerMock要求非系统类*使用*该类在'@ PrepareForTest'中列出(正如其他答案中已经指出的那样)。但根据我的经验,像这样的测试中最大的错误是甚至会引起嘲笑日期/时间戳或系统时钟的想法。如果SUT使用'LocalDateTime.now()',那么它很可能应该允许从外部(通过调用者代码)给出一个日期对象,这将使得编写测试时没有任何嘲讽的微不足道。去过也做过。 –

按照documentation

使用PowerMock.mockStatic(ClassThatContainsStaticMethod.class)嘲笑此类的所有方法。

和:

请注意,您可以模拟一个类的静态方法,即使类是final。该方法也可以是最终的。 若要仅模拟特定类的静态方法,请参阅文档中的partial mocking部分

要嘲笑系统类中的静态方法,您需要遵循this的方法。

你告诉它模拟所有的静态方法,但没有提供of()方法的模拟。

解决方案:添加模拟为of()方法,或改变使用局部嘲笑,所以of()方法没有嘲笑。

基本上,阅读并遵循文档的说明。

所以PowerMockito.mockStatic搞乱了下一行代码。只需移动fixedPointInTime的实例即可在mockStatic ....之前执行,使其一切正常。

@Andreas' answer正确地解释了PowerMock的用法(并且你也计算出了in your own answer)。

我只是想添加一个不同的方法。为了测试当前日期/时间,您可以使用java.time.Clock。通过此课程,您可以创建fixed clock(始终返回相同的当前日期/时间的Clock)和use it in your test。因此,不需要模拟静态方法(我不得不从测试类中删除PowerMock注释)。唯一的区别是,时钟必须传递给now()方法:

@Test 
public void shouldCorrectlyCalculateTimeout() { 
    // create a clock that always returns the same current date/time 
    LocalDateTime fixedPointInTime = LocalDateTime.of(2017, 9, 11, 21, 28, 47); 
    ZoneId zone = ZoneId.systemDefault(); 
    Clock clock = Clock.fixed(fixedPointInTime.atZone(zone).toInstant(), zone); 

    // use the clock in now() method 
    LocalDateTime fixedTomorrow = LocalDateTime.now(clock).plusDays(1); 

    // assert (use equals() instead of == because it doesn't return the same instance) 
    Assert.assertTrue(LocalDateTime.now(clock).equals(fixedPointInTime)); 
    Assert.assertNotNull(fixedTomorrow); 
    Assert.assertEquals(12, fixedTomorrow.getDayOfMonth()); 
} 

我不得不使用equals()方法(而不是==)比较的日期,因为now(clock)创建一个新的实例,尽管所有实例将对应到相同的日期/时间(这是重要的,IMO)。


PS:在上述我使用的JVM默认时区(ZoneId.systemDefault())的代码。唯一的问题是它can be changed without notice, even at runtime,所以最好总是明确你正在使用哪一个。

在这个特定的代码中,时区部分被忽略,因此您可以使用任何区域,这并没有太大的区别 - 除非您在夏令时切换时发生时区和本地日期的组合,这会产生意想不到的结果。

如果您不想依赖于此,则可以替换ZoneId.systemDefault()并改为使用ZoneOffset.UTC(UTC不具有任何夏令时效果)。在这种情况下,你可以创建你的时钟是这样的:

ZoneOffset utc = ZoneOffset.UTC; 
Clock clock = Clock.fixed(fixedPointInTime.toInstant(utc), utc); 

在你的类像

public class SomeClass{ 

    public static void main(String[] args) { 
     LocalDateTime now = getCurrentLocalDateTime(); 
     System.out.println(now); 
    } 

    private LocalDateTime getCurrentLocalDateTime() { 
     return LocalDateTime.now(); 
    } 

} 

创建的方法和在Test类使用:

@PrepareForTest(SomeClass.class) 

@RunWith(PowerMockRunner.class) 

在测试用例:

LocalDateTime tommorow= LocalDateTime.now().plusDays(1); 

SomeClass classUnderTest = PowerMockito.spy(new SomeClass()); 

PowerMockito.when(classUnderTest, "getCurrentLocalDateTime").thenReturn(tommorow);