What Time Is It?

When I got back to work after January 1 our unit tests were failing. I looked into it and found a test with a comment along the lines of “This test will break at the beginning of 2007.” Great.

Here’s a simplified version of the problem:

import java.util.Date;
import java.util.GregorianCalendar;

import junit.framework.TestCase;

public class TestThing extends TestCase {
    public void testFuture() {
        Thing underTest = new Thing();
        GregorianCalendar afterILeaveTheProject = new GregorianCalendar(2007, 0, 1);
        Date date = afterILeaveTheProject.getTime();
        // Here's a Y2K7 problem.
        assertTrue(underTest.isFuture(date));
    }

    public static class Thing {
        public boolean isFuture(Date date) {
            Date today = new Date();
            return today.compareTo(date) < 0;
        }
    }
}

There are a couple of ways to fix this. The easiest is just to update the test and set afterILeaveTheProject to some other date like “January 1, 2008”. We’ve now hidden it in a Someone Else’s Problem field.

Another way to address this is to abstract the way in which we answer the question “what time is it”. What I did the first day back to work this year was to introduce a TimeFactory, and used it like this:

protected void setUp() throws Exception {
    MockTimeFactory timeFactory = new MockTimeFactory(2006, 11, 31);
    TimeFactory.set(timeFactory);
}

protected void tearDown() throws Exception {
    TimeFactory.reset();
}

public void testFuture() {
    Thing underTest = new Thing();
    GregorianCalendar afterILeaveTheProject = new GregorianCalendar(2007, 0, 1);
    Date date = afterILeaveTheProject.getTime();
    // Here's a Y2K7 problem.
    assertTrue(underTest.isFuture(date));
}

This solves the problem at hand, but it could also help unit testing other date-related edge conditions like leap years, statutory holidays, and so on. This might also be useful in a batch processing situation where an overnight batch job should use the same date for “today” throughout the run, or for re-running a batch job later.

This could be implemented in any number of ways such as creating your TimeFactory via Spring or hooking up the TimeFactory to a database. Below is a simple implementation of TimeFactory and MockTimeFactory.

import java.util.Date;
import java.util.GregorianCalendar;

import junit.framework.TestCase;

public class TestThing2 extends TestCase {
    @Override
    protected void setUp() throws Exception {
        MockTimeFactory timeFactory = new MockTimeFactory(2006, 11, 31);
        TimeFactory.set(timeFactory);
    }

    @Override
    protected void tearDown() throws Exception {
        TimeFactory.reset();
    }

    public void testFuture() {
        Thing underTest = new Thing();
        GregorianCalendar afterILeaveTheProject = new GregorianCalendar(2007, 0, 1);
        Date date = afterILeaveTheProject.getTime();
        // Here's a Y2K7 problem.
        assertTrue(underTest.isFuture(date));
    }

    public static class Thing {
        public boolean isFuture(Date date) {
            Date today = TimeFactory.now();
            return today.compareTo(date) < 0;
        }
    }

    public static class TimeFactory {
        private static TimeFactory instance = new TimeFactory();

        public static Date now() {
            return instance._now();
        }

        protected Date _now() {
            return new Date();
        }

        public static void reset() {
            instance = new TimeFactory();
        }

        public static void set(TimeFactory timeFactory) {
            instance = timeFactory;
        }
    }

    public static class MockTimeFactory extends TimeFactory {
        private Date time;

        public MockTimeFactory(int year, int zeroBasedMonth, int day) {
            this.time = new GregorianCalendar(year, zeroBasedMonth, day).getTime();
        }

        @Override
        protected Date _now() {
            return this.time;
        }
    }
}
It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply