6

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?

KiriSakow
  • 957
  • 1
  • 12
  • 22

1 Answers1

2

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().

KiriSakow
  • 957
  • 1
  • 12
  • 22
  • Dear peers, feel free to comment or complete my reply. – KiriSakow Aug 21 '17 at 15:44
  • Good! Just some details: `Clock.system(Clock.systemDefaultZone().getZone())` is redundant, you can simply use `Clock.systemDefaultZone()` - and to get the system default timezone, just use `ZoneId.systemDefault()` (which is the same as `Clock.systemDefaultZone().getZone()` and `java.util.TimeZone.getDefault().toZoneId()`) - just beware of the [drawbacks of using the JVM default timezone](https://stackoverflow.com/a/45797132/7605325). –  Aug 21 '17 at 16:02
  • 1
    And if you don't use `myService` after `doExecute` (or if you use just for things that don't use the `Clock`), there's no need to call `initDefaultClock`. Actually, I'd prefer to make `MyService` immutable (no setters) and create 2 constructors: one without parameters that sets the clock to `Clock.systemDefaultZone()` and another that receives a clock - but now I'm entering in design details and maybe it's beyond the point of testability. –  Aug 21 '17 at 16:18