5

So I have a class that has a method "getDaysUntil(Date date)" which returns the number of days until the date given as parameter. I mention that I cannot change the class below:

public class A {

public int getDaysUntil(Date givenDate) {

    ... // code

    Date currentDate = new Date() //it creates a date object holding the current day

    ...// code that calculates the nr of days between currentDate and givenDate.

}

I have to do some unit testing and you might see the problem, it creates currentDate inside the method and the returned value will be different from day to day. I have tried to mock a Date object or "override" System.currentTimeMillis() with PowerMock but to no avail.

Is there any way to properly test these kind of methods?

Vlad Dobrieski
  • 53
  • 1
  • 1
  • 4
  • 3
    Yes, there are lots of ways to do this. One possible way of doing it is described in [my answer here](http://stackoverflow.com/q/11042200) – Dawood ibn Kareem Mar 03 '17 at 07:27
  • 1
    What @DavidWallace said, basically. And do note that since Java8 there is actually a dedicated class that might help you with that, called `java.time.Clock` - so you don't have to roll your own implementation as suggested in linked answers. – M. Prokhorov Mar 03 '17 at 07:39
  • 1
    It might be me, but from @DavidWallace post, I understand that Ihave to change the code from the original method. And I can't do that. – Vlad Dobrieski Mar 03 '17 at 07:43
  • If you use `ChronoUnit.DAYS.between(LocalDate.now(), otherDate)`, there will not be so much logic to test (substitute appropriate other Java 8 class for `LocalDate` depending on your exact requirements). – Ole V.V. Mar 03 '17 at 07:43
  • The method is hard to test as it stands. One option is, you calculate, say, the day that is 0, 1, 29, 32, 364, etc. days from today, pass it to the method and check that you get the number back you used in your calculation. This will work on any day and requires no mocking/stubbing. Take into account that the method may run over midnight, and if so, the result may be allowed to be off by 1. – Ole V.V. Mar 03 '17 at 07:46
  • 1
    Just curious (and maybe teasing a little bit ;-) why do you want to unit test a class you cannot change? You won’t be able to fix any errors you find anyway… – Ole V.V. Mar 03 '17 at 11:56
  • 1
    @OleV.V. Home assigment – Vlad Dobrieski Mar 03 '17 at 13:43
  • You should know that the troublesome `java.util.Date` class was supplanted years ago by the modern *java.time* classes, specifically the `Instant` class. – Basil Bourque Jun 19 '19 at 17:13

3 Answers3

4

Use a class that serves as a DateFactory, which is called to construct Date objects in your application code.

Then just mock the method of that DateFactory in your unit test. That way you can make it return whatever date you want as a virtual "current date"

Jens Wurm
  • 5,946
  • 1
  • 10
  • 6
1

One solution where System.currentTimeMillis() is mocked is as follows, using the JMockit library (it should be possible with PowerMock too):

@Test @SuppressWarnings("deprecation")
public void daysUntilCurrentDate() {
    final long fakeCurrentDateInMillis = new Date(2017, 2, 1).getTime();
    new MockUp<System>() {
        @Mock long currentTimeMillis() { return fakeCurrentDateInMillis; }
    };
    A tested = new A();

    int daysSinceJan30 = tested.getDaysUntil(new Date(2017, 1, 30));

    assertEquals(2, daysSinceJan3O);
}
Rogério
  • 16,171
  • 2
  • 50
  • 63
0

I understand that you cannot change the method that you need to test. Unfortunately this also means that you are stuck with the old and often not very programmer-friendly Date class (I am assuming java.util.Date).

Edit: The no-arg Date constructor that your method uses in turn uses System.currentTimeMillis(), a static native method. I didn’t know there were tools that could mock contructors and static native methods, but was informed by comment and answer by @Rogério, the developer of JMockit, that such mocking tools exist.

In any case, there is an alternative: you calculate some number of days from today, pass the resulting Date to the method and check that you get the number back you used in your calculation. This will work on any day and requires no mocking/stubbing.

In the code below I am assuming that the getDaysUntil method should discard the hours and minutes and just look at the date in the computer’s time zone. If the real requirements differ, you can probably make the appropriate adjustments to my code.

We want to take into account that the method may run over midnight. If so, I consider the result undefined since we do not know whether the Date object was constructed before or after midnight. In this case I simply try again, assuming the test will finish before the next midnight.

@Test
public void testGetDaysUntil() {
    A instanceUnderTest = new A();
    for (int daysToTest = 0; daysToTest <= 400; daysToTest++) {

        LocalDate today;
        int result;
        do {
            today = LocalDate.now(); // do this in each iteration in case day changes underway
            LocalDate targetDate = today.plusDays(daysToTest);
            Date midnightAtStartOfDay = Date.from(targetDate.atStartOfDay(ZoneId.systemDefault())
                                                    .toInstant());
            result = instanceUnderTest.getDaysUntil(midnightAtStartOfDay);
        } while (! today.equals(LocalDate.now())); // if we have passed midnight, try again
        assertEquals(daysToTest, result);

        do {
            today = LocalDate.now();
            LocalDate targetDate = today.plusDays(daysToTest);
            Date nearMidnightAtEndOfDay = Date.from(targetDate.atTime(23, 59, 59, 400_000_000)
                                                        .atZone(ZoneId.systemDefault())
                                                        .toInstant());
            result = instanceUnderTest.getDaysUntil(nearMidnightAtEndOfDay);
        } while (! today.equals(LocalDate.now()));
        assertEquals(daysToTest, result);
    }
}

I have used the Java 8 classes for the date and time calculations. If you cannot use Java 8, Calendar and/or GregorianCalendar can be used, they may be just a little more cumbersome for this job, but at least can be converted to Date easily.

Community
  • 1
  • 1
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • 1
    But you *can* mock constructors *and also* `native` methods. Check your facts. – Rogério Mar 03 '17 at 17:34
  • I do not see any reason why this great answer should be DVed. – Arvind Kumar Avinash Oct 15 '21 at 10:24
  • 1
    @ArvindKumarAvinash I think the downvote was given at a time when I claimed that you cannot mock the `Date` constructor nor `System.currentTimeMillis()`, which I later learned is possible, and edited my answer accordingly. Then the downvoter didn’t discover the edit. – Ole V.V. Oct 15 '21 at 11:08