Mockito. Capturing a call to LocalDateTime.now();

2019-07-26 09:36发布

问题:

I'm doing a unit test which calls a class and checks that a method in that class has been called with certain parameters. One of those parameters is LocalDateTime.now().

My problem is that by the time control comes back to my JUnit test, time has marched on, so if I try to compare the time used in the class I'm calling with the current time, they ain't gonna match.

This is the method I'm looking at:

ServiceStatus serviceStatus = ServiceStatus.createNew(LocalDateTime.now());

The Junit test is:

verify(service).saveNotice(LocalDateTime.now));

I've tried using TimeFactory to fix the date:

given(timeFactory.currentDateTime()).willReturn(NOW);

followed by:

given(message.getHeader("MESSAGE_CREATED_TIME")).willReturn(NOW.toString());

&

verify(service).saveNotice(NOW));

But no joy.

Any suggestions?

(PS. In the actual code there are other parameters but I've stripped them out for the sake of simplicity and for reasons of commercial confidentiality.)

回答1:

Don't use PowerMock. Use/mock a Clock instead. Switch your call to LocalDateTime.now() to LocalDateTime.now(clock). From the Clock docs:

Best practice for applications is to pass a Clock into any method that requires the current instant. A dependency injection framework is one way to achieve this:

public class MyBean {
  private Clock clock;  // dependency inject
  ...
  public void process(LocalDate eventDate) {
    if (eventDate.isBefore(LocalDate.now(clock)) {
      ...
    }
  }
}

This approach allows an alternate clock, such as fixed or offset to be used during testing.

One strategy there is to make the clock final, and offer two constructors--one that uses a supplied clock, and one that gets the system clock out of Clock.systemDefaultZone() or Clock.systemUTC(). Another is to make the Clock field settable for testing, possibly by leaving it package-private. In either case, you could easily override the value by supplying a Clock created by Clock.fixed(...), or write your own implementation if you need it to change across the duration of the test.



回答2:

I suggest to not use PowerMock.

Your problem is that static call to now(). It might look a bit over-engineered, but seriously consider to simply put some small interface / class around that static call, like:

interface TimeStampProvider {
  LocalDateTime getNow();
}

the "impl" for that can simply call LocalDateTime.now().

But the thing is: now you simply pass a TimeStampProvider object to your class (which you can mock perfectly without the need for PowerMock).

You see: Powermock can lead to all sorts of interesting bizarre problems in the long term; and especially when writing your own code: just put up a better design; instead of trying to "fix" a (partially) broken design using PowerMock.



回答3:

My suggestion is for you to use PowerMockito. This way you can return what you want from the call to LocalDateTime.now().

For that you need more or less the following (example with Calendar class):

        import org.powermock.api.mockito.PowerMockito;
        import org.powermock.core.classloader.annotations.PrepareForTest;
        import org.powermock.modules.junit4.PowerMockRunner;
  //  ...

        @RunWith(PowerMockRunner.class)
        @PrepareForTest({Calendar.class})
        public class DateUtilsTest {

    public void testWithKnownCalendar(){
           PowerMockito.mockStatic(Calendar.class);
           PowerMockito.when(Calendar.getInstance(TimeZone.getTimeZone("UTC"))).thenReturn(CalendarObjectFactory.getInstance().buildKnownCalendar(45)); 

        // your normal code here
    }
        }

Adapt it by using LocalDateTime instead of Calendar.