jun109 / faketime

Fake currentTimeMillis() without class loader hacks

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

FakeTime for Java

FakeTime uses a native Java agent to replace System.currentTimeMillis() implementation with the one you can control using system properties.

Inspired by arvindsv/faketime.

Build Status

public class ExamRegistrationServiceTest implements FakeTimeMixin {
  
  @Autowired
  ExamRegistrationService examRegistrationService;
  
  @Test
  public void registrationExpiresAfterGivenPeriod() {
    ExamRegistration registration = examRegistrationService.openRegistrationValidFor(Duration.ofDays(5));
    
    offsetRealTimeByDays(5);
    
    assertThat(registration.hasExpired()).isTrue();
  }
  
  @Test
  public void registrationIsValidDuringGivenPeriod() {
    ExamRegistration registration = examRegistrationService.openRegistrationValidFor(Duration.ofDays(5));
      
    offsetRealTimeBy(Duration.ofDays(5).minusMinutes(1));
      
    assertThat(registration.hasExpired()).isFalse();
  }
  
  @After
  public void restoreRealTimeAfterTest() {
    restoreRealTime();
  }
}

Manual setup

Start faking time in 4 easy steps:

  1. Download the faketime-agent.jar for your operating system from the Maven Central repository.
<!-- Windows 32bit -->
<dependency>
  <groupId>io.github.faketime-java</groupId>
  <artifactId>faketime-agent</artifactId>
  <version>0.8.0</version>
  <classifier>windows32</classifier>
</dependency>

<!-- Windows 64bit -->
<dependency>
  <groupId>io.github.faketime-java</groupId>
  <artifactId>faketime-agent</artifactId>
  <version>0.8.0</version>
  <classifier>windows64</classifier>
</dependency>

<!-- macOS 32bit -->
<dependency>
  <groupId>io.github.faketime-java</groupId>
  <artifactId>faketime-agent</artifactId>
  <version>0.8.0</version>
  <classifier>mac32</classifier>
</dependency>

<!-- macOS 64bit -->
<dependency>
  <groupId>io.github.faketime-java</groupId>
  <artifactId>faketime-agent</artifactId>
  <version>0.8.0</version>
  <classifier>mac64</classifier>
</dependency>

<!-- Linux 32bit -->
<dependency>
  <groupId>io.github.faketime-java</groupId>
  <artifactId>faketime-agent</artifactId>
  <version>0.8.0</version>
  <classifier>linux32</classifier>
</dependency>

<!-- Linux 64bit -->
<dependency>
  <groupId>io.github.faketime-java</groupId>
  <artifactId>faketime-agent</artifactId>
  <version>0.8.0</version>
  <classifier>linux64</classifier>
</dependency>
  1. Unpack the jar to get the agent, which is faketime.dll on Windows and libfaketime on other systems.
  2. Attach the agent to your Java program with following JVM arguments.
-agentpath:path/to/unpacked/faketime/binary
-XX:+UnlockDiagnosticVMOptions
-XX:DisableIntrinsic=_currentTimeMillis
-XX:CompileCommand=quiet
-XX:CompileCommand=exclude,java/lang/System.currentTimeMillis
-XX:CompileCommand=exclude,jdk/internal/misc/VM.getNanoTimeAdjustment
  1. Use system properties to manipulate System.currentTimeMillis().
System.out.println(System.currentTimeMillis()); // 1234567890
System.setProperty("faketime.offset.ms", "-7890");
System.out.println(System.currentTimeMillis()); // 1234560000

System.setProperty("faketime.absolute.ms", "12345");
System.out.println(System.currentTimeMillis()); // 12345

Java 8 API

<dependency>
  <groupId>io.github.faketime-java</groupId>
  <artifactId>faketime-api</artifactId>
  <version>0.8.0</version>
  <scope>test</scope>
</dependency>

In case you get tired of converting everything to milliseconds there is a Java Time based API.

FakeTime.stopAt(LocalDateTime.of(2000, 11, 10, 9, 8, 7));
FakeTime.stopAt(2000, 11, 10, ZoneOffset.UTC);
FakeTime.offsetRealByMinutes(100);
FakeTime.offsetRealBy(Duration.ofHours(20));
FakeTime.restoreReal();

And in case you get annoyed by writing FakeTime all the time there is a handy mixin.

public class MyTest implements FakeTimeMixin {
  
  @Test
  public void someTimeTest() {
    stopTimeAt(LocalDateTime.of(2000, 11, 10, 9, 8, 7));
    stopTimeAt(2000, 11, 10, ZoneOffset.UTC);
    offsetRealTimeByMinutes(100);
    offsetRealTimeBy(Duration.ofHours(20));
  }
  
  @After
  public void restoreRealTimeAfterTest() {
    restoreRealTime();
  }
}

JUnit rule

<dependency>
  <groupId>io.github.faketime-java</groupId>
  <artifactId>faketime-junit</artifactId>
  <version>0.8.0</version>
  <scope>test</scope>
</dependency>

This rule calls FakeTime.restoreReal() after every test, so you don't have to.

Note: when using faketime-junit you don't need to add faketime-api as a test dependency

public class MyTest implements FakeTimeMixin {
  
  @Rule
  public FakeTimeRule fakeTimeRule = new FakeTimeRule();
  
  @Test
  public void someTimeTest() {
    stopTimeAt(2000, 11, 10);
    
    assertThat(LocalDate.now()).isEqualTo(LocalDate.of(2000, 11, 10));
    
    // next test will start with real time
  }
}

Maven plugin

For further convenience there is a Maven plugin that downloads and unpacks the correct agent for your operating system. It then sets a property that you can use in surefire or failsafe plugins to attach the agent.

Full example here

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>2.22.1</version>
      <configuration>
        <argLine>${faketime.argLine}</argLine>
      </configuration>
    </plugin>
    
    <plugin>
      <groupId>io.github.faketime-java</groupId>
      <artifactId>faketime-maven-plugin</artifactId>
      <version>0.8.0</version>
      <executions>
        <execution>
          <goals>
            <goal>prepare</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Maven + IntelliJ

IntelliJ has a cool feature that reads argLine from pom.xml and adds all arguments to the IDE test runner. The only thing you need to do is to replace ${faketime.argLine} with literal arguments, since IntelliJ is not aware of ${faketime.argLine}.

Note: before running tests from IntelliJ make sure faketime-maven-plugin has downloaded the agent, otherwise tests won't start

Full example here

<properties>
  <faketime.binary>libfaketime</faketime.binary>
</properties>

<profiles>
  <profile>
    <id>faketimeBinary</id>
    <activation>
      <os>
        <family>windows</family>
      </os>
    </activation>
    <properties>
      <faketime.binary>faketime.dll</faketime.binary>
    </properties>
  </profile>
</profiles>

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-failsafe-plugin</artifactId>
      <version>2.22.1</version>
      <configuration>
        <argLine>
          -agentpath:${project.build.directory}/${faketime.binary}
          -XX:+UnlockDiagnosticVMOptions
          -XX:DisableIntrinsic=_currentTimeMillis
          -XX:CompileCommand=quiet
          -XX:CompileCommand=exclude,java/lang/System.currentTimeMillis
          -XX:CompileCommand=exclude,jdk/internal/misc/VM.getNanoTimeAdjustment
        </argLine>
      </configuration>
    </plugin>
    
    <plugin>
      <groupId>io.github.faketime-java</groupId>
      <artifactId>faketime-maven-plugin</artifactId>
      <version>0.8.0</version>
      <executions>
        <execution>
          <goals>
            <goal>prepare</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Maven + Eclipse

Note: before running tests from Eclipse make sure faketime-maven-plugin has downloaded the agent, otherwise tests won't start

Preferences > Java > Installed JREs > Select > Edit > Default VM arguments

# if you're on Windows
-agentpath:target/faketime.dll
-XX:+UnlockDiagnosticVMOptions
-XX:DisableIntrinsic=_currentTimeMillis
-XX:CompileCommand=quiet
-XX:CompileCommand=exclude,java/lang/System.currentTimeMillis
-XX:CompileCommand=exclude,jdk/internal/misc/VM.getNanoTimeAdjustment

# if you're on macOS/Linux
-agentpath:target/libfaketime
-XX:+UnlockDiagnosticVMOptions
-XX:DisableIntrinsic=_currentTimeMillis
-XX:CompileCommand=quiet
-XX:CompileCommand=exclude,java/lang/System.currentTimeMillis
-XX:CompileCommand=exclude,jdk/internal/misc/VM.getNanoTimeAdjustment

Gradle

There are no instructions for Gradle yet, but if you'll figure this out, then don't be shy to make a pull request. Basically, you need to download and unpack the correct agent artifact and then add some JVM arguments to the test runner.

About

Fake currentTimeMillis() without class loader hacks

License:MIT License


Languages

Language:Java 78.1%Language:C 16.7%Language:Shell 3.1%Language:Batchfile 2.1%