简体   繁体   中英

Java Mockito when-return on Object creation

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?

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();
    }
}

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);
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM