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()
withClock.getInstance().getCalendarInstance()
. - Replace
new Date()
withClock.getInstance().newDate()
- Replace
System.currentTimeMillis()
withClock.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
Break or Return from Java 8 Stream Foreach
Jvm Takes a Long Time to Resolve Ip-Address for Localhost
How to Create Custom Methods for Use in Spring Security Expression Language Annotations
Can a Java File Have More Than One Class
Post Request Send JSON Data Java Httpurlconnection
Whitespace Matching Regex - Java
Are Static Variables Shared Between Threads
Clone() VS Copy Constructor VS Factory Method
Java Null Check Why Use == Instead of .Equals()
Spring MVC - How to Get All Request Params in a Map in Spring Controller
Using Generics in Spring Data JPA Repositories
Getting Java Version at Runtime
How Is an Instance Initializer Different from a Constructor