-1

I am trying to Unit test code that uses java.time.LocalDateTime. I'm able to get the mock working, however when I add time (either minutes or days) I end up with a null value.

@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());
    }
}

I understand (well I think I do) that LocalDateTime is immutable, and I would think I should get a new instance instead of the null value.

Turns out it is the .of method that is giving me a null value. Why?

Adam Erstelle
  • 2,454
  • 3
  • 21
  • 35
  • Did you try to run code without mocks? It's likely that powermockito is creating the issue. – Ali Sep 12 '17 at 02:58
  • 1
    And consider to rethink using PowerMock(ito) in the first place ;-) – GhostCat Sep 12 '17 at 06:07
  • When mocking a Java "system" class (like `java.time.LocalDateTime`), PowerMock requires that the non-system class *using* that class gets listed in `@PrepareForTest` (as already pointed out in other answers). But the biggest mistake in tests like this, in my experience, is to even entertain the idea of mocking dates/timestamps or the system clock. If the SUT uses `LocalDateTime.now()`, then it most likely should also allow a date object to be given from outside (by the caller code), which would make it trivial to write the test without any mocking. Been there, done that. – Rogério Sep 13 '17 at 01:40

4 Answers4

5

According to the documentation:

Use PowerMock.mockStatic(ClassThatContainsStaticMethod.class) to mock all methods of this class.

and:

Note that you can mock static methods in a class even though the class is final. The method can also be final. To mock only specific static methods of a class refer to the partial mocking section in the documentation.

To mock a static method in a system class you need to follow this approach.

You told it to mock all the static methods, but didn't supply a mock for the of() method.

Solution: Either add mock for the of() method, or change to use partial mocking, so the of() method is not mocked.

Basically, read and follow the instructions of the documentation.

Community
  • 1
  • 1
Andreas
  • 154,647
  • 11
  • 152
  • 247
4

@Andreas' answer correctly explains about PowerMock usage (and you also figured out in your own answer).

I'd just like to add a different approach. In order to test the current date/time, you could use a java.time.Clock. With this class, you can create a fixed clock (a Clock that always returns the same current date/time) and use it in your test.

With this, there's no need to mock static methods (I had to remove the PowerMock annotations from the test class). The only difference is that the clock must be passed to the now() method:

@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());
}

I had to use equals() method (instead of ==) to compare the dates, because now(clock) creates a new instance, although all instances will correspond to the same date/time (and that's what matters, IMO).


PS: In the code above I'm using the JVM default timezone (ZoneId.systemDefault()). The only problem is that it can be changed without notice, even at runtime, so it's better to always make it explicit which one you're using.

In this specific code, the timezone part is being ignored, so you could use any zone and this wouldn't make much difference - unless you get a combination of a timezone and local date when a Daylight Saving Time changeover occurs, which can produce unexpected results.

If you don't want to rely on that, you could replace ZoneId.systemDefault() and use ZoneOffset.UTC instead (UTC doesn't have any Daylight Saving effects). In this case, you could create your clock like this:

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

So the PowerMockito.mockStatic was messing with the next line of code. Simply moving the instantiation of fixedPointInTime to execute before the mockStatic....makes it all work.

Adam Erstelle
  • 2,454
  • 3
  • 21
  • 35
0

Create a method in your class like

public class SomeClass{

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

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

}

And in the Test Class you use:

@PrepareForTest(SomeClass.class)

@RunWith(PowerMockRunner.class)

In TestCase:

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

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

PowerMockito.when(classUnderTest, "getCurrentLocalDateTime").thenReturn(tommorow);
Nissa
  • 4,636
  • 8
  • 29
  • 37