Testing Times in Java

By: on December 6, 2009

How do you test a Java application that uses the current date or time as the basis for a calculation? I’ve seen a couple of ways:

  1. Make sure all date/time operations use a service class to get the current date, which is then mocked
  2. Adjust the system clock

Option 1) is a PITA for an existing codebase, or if any 3rd party libraries do date processing. Option 2) rules out any sensible automation, especially on a machine used for anything else.

Another option is to mock system date related classes. JMockit is a mock testing framework that uses Java 5 instrumentation to allow bytecode to be modified at runtime. Using this, known date values can be inserted into tests with no interference with production code or rewriting of legacy code. If Java 6 is available, it is possible to go further and mock the System class itself.

We have an example application that performs the onerous task of checking whether a supplied date is the future or not:

public class App {
    public static boolean isInFuture(Date date) {
        return new Date().after(date);
    }
}

To test this under Java 5, we need to mock out the java.util.Date class. First of all, we need to configure JMockit. The project (attached at the bottom of this post) uses Maven for building and testing. The POM is pretty much the standard archetype with the following modifications:

1. JMockit lives at java.net, so their repo needs to be added:

  <repositories>
    <repository>
      <id>download.java.net</id>
      <url>http://download.java.net/maven/2</url>
    </repository>
  </repositories>

2. The library needs to be added as a dependency for testing:

    <dependency>
      <groupId>mockit</groupId>
      <artifactId>jmockit</artifactId>
      <version>0.993</version>
      <scope>test</scope>
    </dependency>

3. Finally, the JMockit JAR needs to be set as the Java agent in order to hook into the instrumentation API. This is done in the Surefire plugin configuration by passing the correct argument to the JVM.

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
            <argLine>-javaagent:"${settings.localRepository}/mockit/jmockit/0.993/jmockit-0.993.jar"</argLine>
        </configuration>
    </plugin>

With JMockit configured, a mock for the java.util.Date class can be created:

1.   @MockClass(realClass = Date.class)
     public class MockDate {
         private Calendar now = null;
         public Date it;

         public MockDate(Calendar now) {
             this.now = now;
         }

2.       @Mock
3.       public void $init() throws Exception {
4.           Field field = Date.class.getDeclaredField("fastTime");
             field.setAccessible(true);
             field.set(it, now.getTimeInMillis());
         }
     }
  1. The MockClass annotation declares the class as containing mock methods or constructors
  2. The Mock annotation declares the following method / constructor as a replacement for that in the real class
  3. The special $init() method overrides the default constructor in the real class
  4. The private field fastTime holds the underlying date represented by a Date object, and so this is set to be the date passed to the mock

The mock object can now be initialised and used in a JUnit test class:

   public class AppTest extends TestCase {
        private static Calendar TODAY = new GregorianCalendar(2009, JANUARY, 5);
        private static Calendar YESTERDAY = new GregorianCalendar(2009, JANUARY, 4);
        private static Calendar TOMORROW = new GregorianCalendar(2009, JANUARY, 6);

        ...

        @Override
        protected void setUp() {
1.          mockit.Mockit.setUpMocks(new MockDate(TODAY));
        }

        @Override
        protected void tearDown() {
2.          mockit.Mockit.restoreOriginalDefinition(Date.class);
        }

        public void testDateInFuture() {
           assertTrue(App.isInFuture(TOMORROW.getTime()));
        }

        public void testDateInPast() {
          assertFalse(App.isInFuture(YESTERDAY.getTime()));
        }
   }
  1. Tells JMockit which classes are to be mocked
  2. Restores mocked classes to the original versions. Failure to do this can lead to interesting and often subtle errors in subsequent tests

The same approach can be taken for the java.util.Calendar class.

Java 6 allows the instrumentation of native methods which means that the System.currentTimeMillis() method can be mocked. This is used in both the java.util.Date or java.util.Calendar classes and so is the simplest way of covering all bases if using Java 6:

@MockClass(realClass = System.class)
public class MockSystem {
    private Calendar now = null;

    public MockSystem(Calendar now) {
        this.now = now;
    }

    @Mock
    public long currentTimeMillis() {
        return now.getTimeInMillis();
    }
}

An example project containing the code can be found here

Share

2 Comments

  1. Kirk Wylie says:

    Or just use the JSR-310 libraries, whose InstantProvider is ideally suited to just this exact problem.

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*