2

I have the following function:

public static Date getFirstOfLastMonth() {
    Calendar cal = Calendar.getInstance();
    cal.add(Calendar.MONTH, -1);
    cal.set(Calendar.DAY_OF_MONTH, 1);

    return cal.getTime();
}

How would I write a unit test to check the value returned by this function is the same as the expected value without using the same logic to generate the expected value?

jerney
  • 2,187
  • 1
  • 19
  • 31
  • If I run that, the result is `Sat Oct 01 16:09:25 EDT 2016`. You probably do not want the time to be `4:09:25pm`, do you? So in my opinion, the method has failed unit testing. – Andreas Nov 17 '16 at 21:10

4 Answers4

3

See other answer for recommendation to use JodaTime or Java 8. However this can be done using java.util.Calendar.

The key is to change your method to pass in the date rather than let it assume the current time. Perhaps consider renaming this method too to reflect its new semantics, see Andreas suggestion of getFirstOfPreviousMonth.

You would need to call this as getFirstOfLastMonth(new Date()) to preserve existing behaviour, perhaps even with default method

public static Date getFirstOfLastMonth() {
     return getFirstOfPreviousMonth(new Date());
}

public static Date getFirstOfPreviousMonth(Date now) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(now);
    cal.add(Calendar.MONTH, -1);
    cal.set(Calendar.DAY_OF_MONTH, 1);

    return cal.getTime();
}

Then some tests, you can use Calendar to inspect the result:

@Test
public void previousYear() {
    Calendar input = Calendar.getInstance();
    input.clear();
    input.set(2009, Calendar.JANUARY, 5);

    Date result = getFirstOfLastMonth(input.getTime());

    Calendar output = Calendar.getInstance();
    output.setTime(result);
    assertThat(output.get(Calendar.YEAR), is(2008));
    assertThat(output.get(Calendar.MONTH), is(Calendar.DECEMBER));
    assertThat(output.get(Calendar.DAY_OF_MONTH), is(1));
}
Adam
  • 35,919
  • 9
  • 100
  • 137
1

Add a method that requires a timestamp to be passed in:

static Date getFirstOfPreviousMonth(long timeInMillis)
{
    Calendar cal = Calendar.getInstance();
    cal.setTimeInMillis( timeInMillis );

    cal.add(Calendar.MONTH, -1);
    cal.set(Calendar.DAY_OF_MONTH, 1);

    return cal.getTime();
}

Now you can test this method by passing specific moments.

And the original method, implement it by calling this package private method with current system time.

public static Date getFirstOfLastMonth() {
    getFirstOfPreviousMonth(System.currentTimeMillis());
}
Alexander Pogrebnyak
  • 44,836
  • 10
  • 105
  • 121
1

The Answer by Adam is correct and should be accepted.

It mentions that you should be (really should be) using the modern java.time classes rather than the troublesome old legacy date-time classes. Here is a version of Adam's code adapted to the java.time classes.

Also, time zone is crucial in determining a date like first of month. For any given moment, the date varies around the globe by zone. For example, a new day dawns earlier in Auckland NZ than Montréal Québec. If omitted, your JVM implicitly applies its current default time zone. That default can change at any moment during runtime. Better to specify explicitly.

Instant

The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).

ZonedDateTime

This Instant class is a basic building-block class of java.time. You can think of OffsetDateTime as an Instant plus a ZoneOffset. Likewise, you can think of ZonedDateTime as an Instant plus a ZoneId.

If you care only about the date and not the time-of-day, use LocalDate class.

TemporalAdjuster

The TemporalAdjuster interface defines classes for manipulating date-time values such as getting first-of-month. The TemporalAdjusters class (note the plural 's') provides implementations such as firstDayOfMonth.

public static ZonedDateTime getFirstOfLastMonth( ZoneId z ) {
    Instant instant = Instant.now();
    return getFirstOfPreviousMonth( instant , z );
}

public static ZonedDateTime getFirstOfPreviousMonth( Instant instantArg , ZoneId zoneArg ) {
    ZonedDateTime zdt = instantArg.atZone( zoneArg);
    ZonedDateTime firstOfMonthZdt = zdt.with( TemporalAdjusters.firstDayOfMonth() );
    ZonedDateTime firstOfMonthPriorZdt = firstOfMonthZdt.minusMonths( 1 );

    return firstOfMonthPriorZdt;
}

Clock

You can pass a special Clock implementation for testing rather than use by default the real-life-time clock implementation. That clock offers static methods to generate several such alternate implementations.

For example, the fixed implementation always supplies the same frozen moment in time. Let's say you wanted the clock to always report the first moment of March 21, 2017 in Québec time.

LocalDate ld = LocalDate.of( 2017 , Month.MARCH , 21 );
ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ld.atStartOfDay( z ); // Let java.time determine first moment of the day. Not always `00:00:00`.
Instant instant = zdt.toInstant();
Clock c = Clock.fixed( instant , z );
// Pass this clock `c` in your calls to various java.time methods.

About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to java.time.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

Where to obtain the java.time classes?

  • Java SE 8 and SE 9 and later
    • Built-in.
    • Part of the standard Java API with a bundled implementation.
    • Java 9 adds some minor features and fixes.
  • Java SE 6 and SE 7
    • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Community
  • 1
  • 1
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
-1

Use Joda or Java 8 libraries. Java <= 7 is very deficient in this way. What you need to do is get underneath Calendar and mock it into thinking the date is some fixed date in time, like May 31st, 2010 or any other. Then your unit test uses hardcoded dates.

There's a bit of a long-winded way to do this with Calendar.

  1. Your method cannot be static.
  2. Your class constructor will take a CalendarFactory that returns calendars and will call factory.get() instead of Calendar.getInstance().
  3. In your test code you will inject a CalendarFactory that outputs a calendar that has already had .set(someFixedDate) called on it.
  4. Then you will unit test against fixed dates.

Believe it or not this is actually what good OO testable code looks like. In my opinion one of the best accomplishments of OO is it lets the developer express testable code. A static method will not make the cut here though, sorry.

djechlin
  • 59,258
  • 35
  • 162
  • 290