Override Java System.Currenttimemillis for Testing Time Sensitive Code

Override Java System.currentTimeMillis for testing time sensitive code

I strongly recommend that instead of messing with the system clock, you bite the bullet and refactor that legacy code to use a replaceable clock. Ideally that should be done with dependency injection, but even if you used a replaceable singleton you would gain testability.

This could almost be automated with search and replace for the singleton version:

  • Replace Calendar.getInstance() with Clock.getInstance().getCalendarInstance().
  • Replace new Date() with Clock.getInstance().newDate()
  • Replace System.currentTimeMillis() with Clock.getInstance().currentTimeMillis()

(etc as required)

Once you've taken that first step, you can replace the singleton with DI a bit at a time.

java.lang.System.currentTimeMillis() replace method

You could use an AspectJ compiler/weaver to compile/weave the problematic user code, replacing the calls to java.lang.System.currentTimeMillis() with your own code. The following aspect will just do that:

public aspect CurrentTimeInMillisMethodCallChanger {

long around():
call(public static native long java.lang.System.currentTimeMillis())
&& within(user.code.base.pckg.*) {
return 0; //provide your own implementation returning a long
}
}

Do we have setCurrentMillisFixed in Java 8?

There are several variants of Clock, you can use Clock.fixed(...) to always return a specified instant.

How to override/control the way the JVM gets the system date?

There are a couple of ways to do this:

  • if you can rewrite code - as mentioned in some other answers, basically you need a configurable NowProvider, DateFactory, or other strategy (I like injectable NowProvider.now() as a dropin replacement for System.currentTimeMillis myself)

  • Powermock, and some other toolkits let you use ClassLoader tricks to override static methods, even system methods

Writing and testing convenience methods using Java 8 Date/Time classes

In your test (and only during test!) set the clock that your convenience class uses so that you can predict the desired/expected results independently of the computer clock:

public class ConvenienceTest {

@Test
public void testGetLocalHour() {
Convenience underTest = new Convenience();

ZoneId zone = ZoneId.of("America/Los_Angeles");

ZonedDateTime fixedZdt = ZonedDateTime.now(zone).withHour(0);
underTest.setClock(Clock.fixed(fixedZdt.toInstant(), zone));
assertEquals("24", underTest.getLocalHour24HourClock());

fixedZdt = fixedZdt.withHour(1);
underTest.setClock(Clock.fixed(fixedZdt.toInstant(), zone));
assertEquals("01", underTest.getLocalHour24HourClock());

fixedZdt = fixedZdt.withHour(23);
underTest.setClock(Clock.fixed(fixedZdt.toInstant(), zone));
assertEquals("23", underTest.getLocalHour24HourClock());

// TODO test with other time zones
}

}

For this to work it of course requires that your convenience class can accept a Clock and uses it:

public class Convenience {

private Clock clock = Clock.systemDefaultZone();

/** For testing only. Sets the clock from which to get the time. */
void setClock(Clock clockForTest) {
this.clock = clockForTest;
}

public String getLocalHour24HourClock() {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("kk");
ZonedDateTime zdt = ZonedDateTime.now(clock);
return zdt.format(fmt);
}

}

With this implementation the tests just passed on my computer.

Stepping a step back: if your convenience methods are but a thin layer on top of java.time, you may start to consider how much value a unit test has. If it is doing some real work (like formatting in the above example, something that regularly goes wrong), a test is valuable, but if a method just returns a value from a single call into java.time, you may not need a test. You shouldn’t test java.time, preferably only your own code.

How can I Unit test that a specific method of a class in java was periodically invoked for a given duration

You can do it by mocking doSomething() method and providing your own implementation that records the time the method gets called. Here's an example:

final List<LocalDateTime> list = new ArrayList<>();
Service service = Mockito.mock(Service.class);
Mockito.doAnswer(a -> list.add(LocalDateTime.now())).when(service).doSomething();

In this example each time the method is called, LocalDateTime representing the current timestamp is saved to a list.
Finally, all you have to do is to loop through the list, compare neighbouring dates, find the intervals and compare them with the expected value. The interval between the first and the last date will show you the total duration.

Remove time & date dependency on os in java application

If you use Java 8 and the classes of java.time, you can use Clock. At normal application run time you can then inject and use the normal system clock provided by Clock.systemDefaultZone(), for tests you can inject a Clock instance with a fixed time, for example provided by Clock.fixed or subclassing Clock if you need more control.

You would then need to change your code to something like:

// Inject this value somehow, eg using Spring
private Clock clock;

public LocalDateTime methodThatGeneratesTime() {
return LocalDateTime.now(clock); // Instead of just LocalDateTime.now()
}


Related Topics



Leave a reply



Submit