[TUT] How to test time and control System.currentTimeMillis

This post explains how to control time and specifically control System.currentTimeMillis. To make your code more flexible, maintainable and in the end testable! Showing an example application that uses time to define different behaviour for the app in the morning and in the evening. Going on to unit test this in a robust manner.

What we are going to do:

– Create a sample app based on am/pm
– Attempt to test this and discuss why we can’t
– Re-architect so that we control time!
– Unit test to confirm our working app

Ok here .. we .. go

Let’s make a simple application that uses time to decide what to show on the screen. This app will display a different message depending if the time is before midday (12 noon) or after midday. Lets also change the background color. Although this may not be your use-case, it is the simplest example of how an application can have branching code that does one thing if the current time is X and does something different if the current time is Y, so hopefully you can relate to this.

Here are some screenshots so you can see the finished app:
(Nexus 1 for the nostalgia)

To create an application like this is straightforward however to create it in a testable way requires a little more thought. In the git repo linked at the bottom of this post shows the finished code. To start with here I’ll show a naive way we can get this app working.

Lets create a WelcomeMessageGenerator this class will be responsible for deciding which welcome message the app has, depending on the time of day.

WelcomeMessageGenerator.java

class WelcomeMessageGenerator {

    private static final WelcomeMessage AM_INFO = new WelcomeMessage(R.string.good_morning, R.color.morning_blue);
    private static final WelcomeMessage PM_INFO = new WelcomeMessage(R.string.good_evening, R.color.evening_red);

    public WelcomeMessage generate() {
        Date now = new Date(System.currentTimeMillis());
        Date midday = getMiddayToday();
        if (now.after(midday)) {
            return PM_INFO;
        } else {
            return AM_INFO;
        }
    }

    private Date getMiddayToday() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 12);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        return calendar.getTime();
    }

}

The generate method creates a date at the current time today, and another date at midday today. If the current time is after midday it returns the PM message otherwise it returns the AM message. WelcomeMessage is just a value class holding text and color resource ids. To use WelcomeMessageGenerator in our MainActivity we will instantiate it and call generate.

WelcomeMessage.java

class WelcomeMessage {
    @StringRes
    private final int textResId;
    @ColorInt
    private final int colorResId;

    public WelcomeMessage(int textResId, int colorResId) {
        this.textResId = textResId;
        this.colorResId = colorResId;
    }

    public int getTextResId() {
        return textResId;
    }

    public int getColorResId() {
        return colorResId;
    }

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_activity);

        WelcomeMessageGenerator generator = new WelcomeMessageGenerator();
        WelcomeMessage welcomeMessage = generator.generate();
        TextView textView = (TextView) findViewById(R.id.main_text_view);
        textView.setText(welcomeMessage.getTextResId());
        textView.setBackgroundResource(welcomeMessage.getColorResId());
    }
}

Ta da! We have a working app just like the screenshots above. Being good developers we now want to test this implementation. How do we do that?

If we tested this using Espresso and Acceptance tests or we tested this with JUnit and Unit tests we have the same problem. We cannot control time therefore we cannot control if the AM text is shown or the PM text. This is untestable code! Well not completely untestable…

Hack: In our test we could also check what the current time is and if it is before midday we assert the morning string and if it is after midday we assert the evening string. Ah ha testable after all! But wait … who tests the code that is written in the tests? Once again you come to the same problem.

Say we went ahead and tried to write the tests that would verify this code. They would look something like the below. Remember with these test only one of them will ever pass at any time of day therefore you will always have a broken test. Not cool!

public class WelcomeMessageGeneratorTest {

    @Test
    public void givenItIsBeforeMidday_thenBlueMorningGreeting() throws Exception {
        WelcomeMessageGenerator generator = new WelcomeMessageGenerator();

        WelcomeMessage message = generator.generate();

        assertEquals(R.string.good_morning, R.color.morning_blue, message);
    }

    @Test
    public void givenItIsAfterMidday_thenRedEveningGreeting() throws Exception {
        WelcomeMessageGenerator generator = new WelcomeMessageGenerator();

        WelcomeMessage message = generator.generate();

        assertEquals(R.string.good_evening, R.color.evening_red, message);
    }

    private void assertEquals(int textResId, int colorResId, WelcomeMessage message) {
        Assert.assertEquals("Wrong text resource id!", textResId, message.getTextResId());
        Assert.assertEquals("Wrong color resource id!", colorResId, message.getColorResId());
    }

}

Having broken tests is never good. If we dive a little bit deeper into the code to determine what we need to fix to make the tests pass the one line we want to test is this branch right here:

        if (now.after(midday)) {
            return PM_INFO;
        } else {
            return AM_INFO;
        }

Looking at this code, now and midday are of type Date. The midday variable is a constant part of our business rule. The rule being “if the time is after midday show a different message.” but ‘the timenow is a changing variable. So lets take a closer look at that.

 
Date now = new Date(System.currentTimeMillis());

This is the reason our code is untestable, it takes the time straight from the system as a static method. If we re-architectured our class to not have this hidden dependency and coupling to the system we could then test our code.

Lets try this. System.currentTimeMillis() is a representation of time, its the current time when that code is executed. In real life time is usually read from a clock. If we make our WelcomeMessageGenerator depend on a clock rather than on the system we could pass a test double clock in our tests and a real clock when the app is running.

WelcomeMessageGenerator.java (with Clock)

class WelcomeMessageGenerator {

    private static final WelcomeMessage AM_INFO = new WelcomeMessage(R.string.good_morning, R.color.morning_blue);
    private static final WelcomeMessage PM_INFO = new WelcomeMessage(R.string.good_evening, R.color.evening_red);

    private final Clock clock;

    public WelcomeMessageGenerator(Clock clock) {
        this.clock = clock;
    }

    public WelcomeMessage generate() {
        Date now = new Date(clock.currentTimeMillis());
        Date midday = getMiddayToday();
        if (now.after(midday)) {
            return PM_INFO;
        } else {
            return AM_INFO;
        }
    }

    private Date getMiddayToday() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 12);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        return calendar.getTime();
    }

}

Great! Now we’re are asking the clock for the current time in millis rather than statically asking the system. The implementation of this clock is very straight forward:

Clock.java

class Clock {

    public long currentTimeMillis() {
        return System.currentTimeMillis();
    }

}

Coming back to our tests we should be able to substitute in the clock that uses the system time for another clock that uses a time that we control. In this way we can set the time to whatever we want it to be and have each of our tests run its own clock. We have become time lords!

WelcomeMessageGeneratorTest.java

public class WelcomeMessageGeneratorTest {

    @Test
    public void givenItIsBeforeMidday_thenBlueMorningGreeting() throws Exception {
        SettableClock clock = new SettableClock();
        clock.time = getHourMinuteSecondAsTimeInMillis(11, 59, 59);
        WelcomeMessageGenerator generator = new WelcomeMessageGenerator(clock);

        WelcomeMessage message = generator.generate();

        assertEquals(R.string.good_morning, R.color.morning_blue, message);
    }

    @Test
    public void givenItIsAfterMidday_thenRedEveningGreeting() throws Exception {
        SettableClock clock = new SettableClock();
        clock.time = getHourMinuteSecondAsTimeInMillis(12, 0, 1);
        WelcomeMessageGenerator generator = new WelcomeMessageGenerator(clock);

        WelcomeMessage message = generator.generate();

        assertEquals(R.string.good_evening, R.color.evening_red, message);
    }

    private long getHourMinuteSecondAsTimeInMillis(int hour, int minute, int second) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, second);
        return calendar.getTimeInMillis();
    }

    private void assertEquals(int textResId, int colorResId, WelcomeMessage message) {
        Assert.assertEquals("Wrong text resource id!", textResId, message.getTextResId());
        Assert.assertEquals("Wrong color resource id!", colorResId, message.getColorResId());
    }

    private class SettableClock extends Clock {
        private long time;

        @Override
        public long currentTimeMillis() {
            return time;
        }
    }
}

Notice how we have created the clock above. We have extended the original clock and overridden the functionality of the currentTimeMillis method so that we can return whatever long we want instead of using System.currentTimeMillis. You could have created your clock in other ways, for example you could have had an interface called Clock and in the app the implementation would be SystemClock and in the tests the implementation would SettableClock. It’s up to you and choice usually depends on circumstance of other code.

Now that our system is testable it highlights one last missing test. What does our app do when the time is exactly twelve midday! Lets write a test to document that as well.


    @Test
    public void givenItIsMidday_thenBlueMorningGreeting() throws Exception {
        SettableClock clock = new SettableClock();
        clock.time = getHourMinuteSecondAsTimeInMillis(12, 0, 0);
        WelcomeMessageGenerator generator = new WelcomeMessageGenerator(clock);

        WelcomeMessage message = generator.generate();

        assertEquals(R.string.good_morning, R.color.morning_blue, message);
    }

I hope this gives a clear explanation of how you can control time and an understanding that inline static methods are a code smell of a hidden coupling from one piece of code to another. Avoiding these gives you cleaner more testable code.

Code is available on GitHub here.

Any questions just ask!

Leave a Reply

Your email address will not be published. Required fields are marked *