Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing time-based logic in Java

I have a method which implements different logic on data fetched from a DB depending on what the current date is.

I want to test it by having the unit test create objects, save them in the DB and invoke the tested method. However, in order to have predictable results I need to change the system date each time and I don't know how to do that in Java.

Suggestions?

like image 251
Alex Avatar asked Feb 23 '12 09:02

Alex


3 Answers

You can generate your expected results using the current date.

Or you write your system to use a date/time you give it when being tested (rather than the clock) That way the time is always what the test expects.

I use something like

interface TimeSource {
    long currentTimeMS(); // actually I have currentTimeNS
    void currentTimeMS(long currentTimeMS);
}

enum VanillaTimeSource implements TimeSource {
    INSTANCE;

    @Override
    public long currentTimeMS() {
        return System.currentTimeMillis();
    }

    @Override
    public void currentTimeMS(long currentTimeMS) {
        // ignored
    }
}

class FixedTimeSource implements TimeSource {
    private long currentTimeMS;
    @Override
    public long currentTimeMS() {
        return currentTimeMS;
    }

    @Override
    public void currentTimeMS(long currentTimeMS) {
        this.currentTimeMS =              currentTimeMS;
    }
}

In tests I use a FixedTimeSource which can be data driven e.g. set by inputs/events. In production I use a VanillaTimeSource.INSTANCE which ignores times in inputs/events and uses the current time.

like image 130
Peter Lawrey Avatar answered Sep 24 '22 02:09

Peter Lawrey


You need to look at injecting something into your class that allows you to customize the way Time is presented.

For example

public interface TimeProvider {
   DateTime getCurrentTime();
}

public class UnderTest {

  // Inject this in some way (e.g. provide it in the constructor)
  private TimeProvider timeProvider;

  public void MyMethod() {
     if (timeProvider.getCurrentTime() == "1234") {
        // Do something
     }
  }
}

Now in your unit tests you can provide a fake implementation of time provider. In the real production code you can just return the current date time.

like image 44
Jeff Foster Avatar answered Sep 24 '22 02:09

Jeff Foster


I had a similar problem recently with code I couldn't refactor too much (time constraints, didn't want to inadvertently break anything). It had a method I wanted to test which called System.currentTimeMillis() and the case I wanted to test would depend on what that value returned. Something like:

public class ClassINeedToTest {
    public boolean doStuff() {
        long l = System.currentTimeMillis();
        // do some calculation based on l
        // and return the calculation
    }
}

To allow unit-testing, I refactored the class so it had a helper method which was protected

protected long getCurrentTimeMillis() {
     // only for unit-testing purposes
     return System.currentTimeMillis();
}

and this method was called by doStuff(). This didn't change the functionality but now meant that when I call it in the unit-test, I could then override this to return a specific value, like

ClassINeedToTest testClass = new ClassINeedToTest() {
    protected long getCurrentTimeMillis() {
        // return specific date for my test
        return 12456778L;
    }
}; 
boolean result = testClass.doStuff();
// test result with an assert here

This does however mean that I've polluted the interface of my class, so you may decide the cost is too high. There are probably better ways if you can refactor the code more.

like image 44
matt freake Avatar answered Sep 21 '22 02:09

matt freake