Search code examples
junitjava-8junit4easymockjava-time

How to override current system time to a specific date in a Java 8 web application with EasyMock, without Joda Time, and without PowerMock?


I need to mock time (to a specific date, meaning I may not use anyLong()) for testing purposes in Java 8 using solely java.time, thus without Joda Time, in a testing environment under EasyMock.

EasyMock doesn't let you mock static methods like LocalDateTime.now(). PowerMock does, but I am not allowed to use PowerMock.

Joda Time library provides a neat and elegant way to mock time with DateTimeUtils.setCurrentMillisFixed(), and DateTimeUtils.setCurrentMillisSystem() to get back to the present instant.

Only I am not allowed to use Joda Time.

I was told to use a mock clock java.time.Clock class instance and inject it into LocalDateTime.now() like this: LocalDateTime.now(mockClock)

Is there a way to implement it properly without Joda Time and without PowerMock?


Solution

  • It happens there is a way. Here's what you need to do:

    What needs to be done in the tested class

    Step 1

    Add a new java.time.Clock attribute to the tested class MyService and make sure the new attribute will be initialized properly at default values with an instantiation block or a constructor:

    import java.time.Clock;
    import java.time.LocalDateTime;
    
    public class MyService {
      // (...)
      private Clock clock;
      public Clock getClock() { return clock; }
      public void setClock(Clock newClock) { clock = newClock; }
    
      public void initDefaultClock() {
        setClock(
          Clock.system(
            Clock.systemDefaultZone().getZone() 
            // You can just as well use
            // java.util.TimeZone.getDefault().toZoneId() instead
          )
        );
      }
      { 
        initDefaultClock(); // initialisation in an instantiation block, but 
                            // it can be done in a constructor just as well
      }
      // (...)
    }
    

    Step 2

    Inject the new attribute clock into the method which calls for a current date-time. For instance, in my case I had to perform a check of whether a date stored in database happened before LocalDateTime.now(), which I replaced with LocalDateTime.now(clock), like so:

    import java.time.Clock;
    import java.time.LocalDateTime;
    
    public class MyService {
      // (...)
      protected void doExecute() {
        LocalDateTime dateToBeCompared = someLogic.whichReturns().aDate().fromDB();
        while (dateToBeCompared.isBefore(LocalDateTime.now(clock))) {
          someOtherLogic();
        }
      }
      // (...) 
    }
    

    What needs to be done in the test class

    Step 3

    In the test class, create a mock clock object and inject it into the tested class's instance just before you call the tested method doExecute(), then reset it back right afterwards, like so:

    import java.time.Clock;
    import java.time.LocalDateTime;
    import java.time.OffsetDateTime;
    import org.junit.Test;
    
    public class MyServiceTest {
      // (...)
      private int year = 2017;  // Be this a specific 
      private int month = 2;    // date we need 
      private int day = 3;      // to simulate.
    
      @Test
      public void doExecuteTest() throws Exception {
        // (...) EasyMock stuff like mock(..), expect(..), replay(..) and whatnot
    
        MyService myService = new MyService();
        Clock mockClock =
          Clock.fixed(
            LocalDateTime.of(year, month, day, 0, 0).toInstant(OffsetDateTime.now().getOffset()),
            Clock.systemDefaultZone().getZone() // or java.util.TimeZone.getDefault().toZoneId()
          );
        myService.setClock(mockClock); // set it before calling the tested method
    
        myService.doExecute(); // calling tested method 
    
        myService.initDefaultClock(); // reset the clock to default right afterwards with our own previously created method
    
        // (...) remaining EasyMock stuff: verify(..) and assertEquals(..)
        }
      }
    

    Check it in debug mode and you will see the date of 2017 Feb 3 has been correctly injected into myService instance and used in the comparison instruction, and then has been properly reset to current date with initDefaultClock().