Henry
Henry

Reputation: 1775

Writing and testing convenience methods using Java 8 Date/Time classes

I have some old convenience methods, written using Calendars, that I want to update to use the Java.time.* classes introduced in Java 8. Some of the methods in my class get quantities like the current time or even just the current hour.

I plan to write two variants of each method: one that assumes that the timezone is the default defined on this computer and one that allows the user to specify the desired timezone.

I'm trying to figure out two main things:

  1. How to get the current timestamp in my methods.
  2. How to unit test my results using a different source of the current timestamp.

With regards to the methods themselves, I'm inclined to create a ZonedDateTime as follows:

LocalDateTime currentDateTime = LocalDateTime.now();
ZoneId timeZoneDefault = ZoneId.systemDefault(); 
ZonedDateTime currentZonedDateTimeDefault = ZonedDateTime.of(currentDateTime, timeZoneDefault);

I would then use a DateTimeFormatter to obtain the part of the result that I actually cared about. For instance, if I wanted the current hour from the 24 hour clock and then from the 12 hour clock, I would do this:

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("kk");
System.out.println("currentHour (24 hour clock): " + currentZonedDateTimeDefault.format(fmt));
DateTimeFormatter fmt2 = DateTimeFormatter.ofPattern("hh a");
System.out.println("currentHour (12 hour clock): " + currentZonedDateTimeDefault.format(fmt2));

Is that okay so far or should I be using a different approach, perhaps using Instant?

My major problem is figuring out a different way to get the current time for my unit tests so that I can compare expected results with actual results. I've been reading about Clocks and Instants and so forth and the more I read, the more I get confused. Some approaches seem to be getting the current time from the same source so they will presumably show that the expected result is ALWAYS the same as the actual result - but then BOTH could be wrong in the same way.

Can someone guide me on the best way to compute the current time in a given timezone that gets the time in a different way than the way I am using in my method?

UPDATE: I'm working on my convenience methods and am using this in each method that starts out by getting the current date and time:

    LocalDateTime now = LocalDateTime.now();
    ZonedDateTime nowLocalTimeZone = ZonedDateTime.of(now, ZoneId.systemDefault());

Then, I get the part of the date/time that I actually want with code like this:

try {
        DateTimeFormatter format = DateTimeFormatter.ofPattern(MONTH_NUMBER_FORMAT);
        return Integer.parseInt(nowLocalTimeZone.format(format));
    }
    catch (DateTimeParseException excp) {
        throw excp;         
    }

Note that within the methods, everything is based on LocalDateTime.now() with NO clock specified.

In my unit tests, I've got this as a class variable:

    private Clock localClock = Clock.fixed(Instant.now(), ZoneId.systemDefault());

Within a typical unit test, I have code like this:

@Test
@DisplayName ("getCurrentMonthNumberLocal()")
void testGetCurrentMonthNumberLocal() {

    LocalDateTime now = LocalDateTime.now(localClock);
    ZonedDateTime nowLocalTimeZone = ZonedDateTime.of(now, ZoneId.systemDefault());

    //Normal cases
    assertEquals(nowLocalTimeZone.getMonthValue(), CurrentDateTimeUtils.getCurrentMonthNumberLocal());
}

Note that this approach involves using the fixed Clock named localClock as the source of the expected result.

When I run the tests, the expected results match the actual result (at least for the few classes I've run so far, which look for year and month number).

Does this approach ensure that I'm getting the current time from two DIFFERENT sources so that the comparison is valid? Or am I effectively getting the same information from the exact same source, such as the system clock on my computer?

I very much want the date/time I get from the methods to come from a different source than the date/time used to determine the expected results in my unit tests so that I'm sure it's a valid test, otherwise, I don't see much point in testing these methods. (I suppose I could just display the results of each function and see if it seems approximately right. It's pretty easy to tell if the year, month, and day are right unless perhaps you are very close to the end of that year, month or day, in which case you might actually be seeing the values for a different time zone and not know it.)

Upvotes: 0

Views: 2397

Answers (1)

Anonymous
Anonymous

Reputation: 86296

In your test (and only during test!) set the clock that your convenience class uses so that you can predict the desired/expected results independently of the computer clock:

public class ConvenienceTest {

    @Test
    public void testGetLocalHour() {
        Convenience underTest = new Convenience();

        ZoneId zone = ZoneId.of("America/Los_Angeles");

        ZonedDateTime fixedZdt = ZonedDateTime.now(zone).withHour(0);
        underTest.setClock(Clock.fixed(fixedZdt.toInstant(), zone));
        assertEquals("24", underTest.getLocalHour24HourClock());

        fixedZdt = fixedZdt.withHour(1);
        underTest.setClock(Clock.fixed(fixedZdt.toInstant(), zone));
        assertEquals("01", underTest.getLocalHour24HourClock());

        fixedZdt = fixedZdt.withHour(23);
        underTest.setClock(Clock.fixed(fixedZdt.toInstant(), zone));
        assertEquals("23", underTest.getLocalHour24HourClock());

        // TODO test with other time zones
    }

}

For this to work it of course requires that your convenience class can accept a Clock and uses it:

public class Convenience {

    private Clock clock = Clock.systemDefaultZone();

    /** For testing only. Sets the clock from which to get the time. */
    void setClock(Clock clockForTest) {
        this.clock  = clockForTest;
    }

    public String getLocalHour24HourClock() {
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("kk");
        ZonedDateTime zdt = ZonedDateTime.now(clock);
        return zdt.format(fmt);
    }

}

With this implementation the tests just passed on my computer.

Stepping a step back: if your convenience methods are but a thin layer on top of java.time, you may start to consider how much value a unit test has. If it is doing some real work (like formatting in the above example, something that regularly goes wrong), a test is valuable, but if a method just returns a value from a single call into java.time, you may not need a test. You shouldn’t test java.time, preferably only your own code.

Upvotes: 1

Related Questions