1

I'm trying to test a class that calculates age. The method that calculates the age looks like this:

public static int getAge(LocalDate birthdate) {
    LocalDate today = new LocalDate();
    Period period = new Period(birthdate, today, PeriodType.yearMonthDay());
    return period.getYears();
}

Since I want the JUnit to be time-independent I want the today variable to always be January 1, 2016. To do this I tried going the Mockito.when route but am running into trouble.

I first had this:

public class CalculatorTest {

    @Before
    public void setUp() throws Exception {
        LocalDate today = new LocalDate(2016,1,1);

        Mockito.when(new LocalDate()).thenReturn(today);
    }
}

But to that I got this error:

org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
   Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.

So then I tried to make a method inside the Calculator class to return the current date like so:

public static LocalDate getCurrentDate() {
    return new LocalDate();
}

public static int getAge(LocalDate birthdate) {
    LocalDate today = getCurrentDate();
    Period period = new Period(birthdate, today, PeriodType.yearMonthDay());
    return period.getYears();
}

So that I could do this:

public class CalculatorTest {

    @Before
    public void setUp() throws Exception {
        CalculatorTest mock = Mockito.mock(CalculatorTest.class);
        LocalDate today = new LocalDate(2016,1,1);

        Mockito.when(mock.getCurrentDate()).thenReturn(today);
    }
}

But to that I get the exact same problem. So any ideas on how to return a predefined localdate object whenever the age calculation is triggered?

Richard
  • 5,840
  • 36
  • 123
  • 208

3 Answers3

4

Instead of mocking, I'd suggest using Joda's DateTimeUtils to "freeze" the time. You would also need to use org.joda.time.LocalDate instead of java.time.LocalDate in your application code.

public class CalculatorTest {

    @Before
    public void setUp() throws Exception {
        DateTimeUtils.setCurrentMillisFixed(new LocalDate(2016,1,1).toDateTimeAtStartOfDay().getMillis());
    }

    @After
    public void tearDown() {
        DateTimeUtils.setCurrentMillisSystem();
    }
}

For pure Java, consider some approaches described here, particularly, injecting a Clock or using PowerMock.

Injecting a Clock is quite similar to the Joda example; you just need to maintain your own static Clock. Your application code would look like this:

static Clock appClock = Clock.systemDefaultZone();

public static int getAge(LocalDate birthdate) { 
  LocalDate today = LocalDate.now(appClock);
  Period period = new Period(birthdate, today, PeriodType.yearMonthDay());
  return period.getYears(); 
}

And the test would freeze the time like this:

public class CalculatorTest {

    @Before
    public void setUp() {
       appClock = Clock.fixed(LocalDate(2016,1,1).toDateTimeAtStartOfDay(), ZoneId.systemDefault());
    }

    @After
    public void tearDown() {
       appClock = Clock.systemDefaultZone();
    }
}
Community
  • 1
  • 1
Yosef Weiner
  • 5,432
  • 1
  • 24
  • 37
  • DateMidnight is deprecated, should I use `LocalDate` instead? – Richard Jan 05 '16 at 21:18
  • @Richard are you thinking of [toDateTimeAtMidnight](http://joda-time.sourceforge.net/apidocs/org/joda/time/LocalDate.html#toDateTimeAtMidnight())? – Yosef Weiner Jan 05 '16 at 21:20
  • no i tried `DateMidnight` and intelliJ says its deprecated. – Richard Jan 05 '16 at 21:21
  • You're right, I was looking at an earlier revision. `LocalDate` won't work directly because it doesn't translate to an `Instant`. Can probably use start of day. Let me update. – Yosef Weiner Jan 05 '16 at 21:22
  • I just tried this and it worked: `DateTimeUtils.setCurrentMillisFixed(new DateTime(2015,2,1, 0, 0).getMillis());` – Richard Jan 05 '16 at 21:23
  • Yes that will work, but isn't that the same thing as saying midnight logically? Either way, I updated my code to use `LocalDate.toDateTimeAtStartOfDay()`, but `DateTime` with 0 hours and 0 minutes should be fine too. – Yosef Weiner Jan 05 '16 at 21:25
  • @Richard I just added a pure Java example with `Clock` – Yosef Weiner Jan 05 '16 at 22:20
1

I suggest you to trick to test your method:

public static int getAge(LocalDate currDate, LocalDate birthdate) {
    Period period = new Period(birthdate, currDate, PeriodType.yearMonthDay());
    return period.getYears();
}

public static int getAge(LocalDate birthdate) {
    return getAge(new LocalDate (), birthdate);
}
Valijon
  • 12,667
  • 4
  • 34
  • 67
0

I allways use my own TimeFactory to retrieve the currentdate in my application. This way I am flexible on manipulating it (during JUnit testing). This is what the TimeFactory looks like:

public final class TimeFactory {

    static int offsetAmount = 0;
    static TemporalUnit offsetUnit = ChronoUnit.MILLIS;

    public static LocalDateTime getLocalDateTimeNow() {
        return LocalDateTime.now().plus(offsetAmount, offsetUnit);
    }

    public static void reset() {
        offsetAmount = 0;
    }

    public static void travelToPast(int amount, TemporalUnit unit) {
        offsetAmount = amount * -1;
        offsetUnit = unit;
    }

    public static void travelToFuture(int amount, TemporalUnit unit) {
        offsetAmount = amount;
        offsetUnit = unit;
    }
}

Using this TimeFactory I can easily do Time travelling:

// start test
TimeFactory.travelToPast(10, ChronoUnit.HOURS);
// continue tests
TimeFactory.reset();
// check that things have happend in the past.

You can expand this code to fix the time.