1

Consider a DateUtilTest class as follows that uses PowerMockRunner:

import com.reporting.utils.DateUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.time.LocalDate;
import java.util.Date;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import static org.powermock.api.mockito.PowerMockito.mockStatic;

@PrepareForTest(DateUtil.class)
@RunWith(PowerMockRunner.class)
public class DateUtilTest {


    @Test
    public void getPreviousWorkingDayAsDate_whenMonday() {
        //given
        LocalDate date = LocalDate.of(2017, 10, 16);
        LocalDate expected = LocalDate.of(2017, 10, 13);

        mockStatic(LocalDate.class);
        when(LocalDate.now()).thenReturn(date);

        //when
        Date previousWorkingDay = DateUtil.getPreviousWorkingDayAsDate();


        //then
        assertEquals(DateUtil.getDateFromLocalDate(expected), previousWorkingDay);
    }

    @Test
    public void getPreviousWorkingDayAsDate2_whenMonday() {
        //given
        LocalDate date = LocalDate.of(2017, 10, 16);


        mockStatic(LocalDate.class);
        when(LocalDate.now()).thenReturn(date);

        //when
        Date previousWorkingDay = DateUtil.getPreviousWorkingDayAsDate();
        LocalDate expected = LocalDate.of(2017, 10, 13);


        //then
        assertEquals(DateUtil.getDateFromLocalDate(expected), previousWorkingDay);
    }

}

I want to understand why @Test ==> getPreviousWorkingDayAsDate2_whenMonday fails when I move the expected LocalDate initialization to after the mocking of the LocalDate.class?

Further, could this tests be improved?

M06H
  • 1,675
  • 3
  • 36
  • 76

3 Answers3

3

Further, could this tests be improved?

Yes - refactor DateUtil to be able to use a specific Clock. For example:

public class DateUtil {
    private static Clock clock = Clock.systemDefaultZone();

    public static setClock(Clock clock) {
        assertNotProduction();  // optionally check for an environment/system variable to throw exception if used in production
        DateUtil.clock = clock;  
    }

   public static Date getPreviousWorkingDayAsDate() {
      LocalDate today = LocalDate.now(clock);   // use clock
      ...
     return ...;
   }
}

Then the unit test does not need any mocking. For example:

@Test
public void getPreviousWorkingDayAsDate_whenMonday() {
    //given
    LocalDate monday = LocalDate.of(2017, 10, 16);
    Clock clock = Clock.fixed(monday.atStartOfDay(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault());
    DateUtil.setClock(clock);

    LocalDate lastFriday = LocalDate.of(2017, 10, 13);

    //when
    Date previousWorkingDay = DateUtil.getPreviousWorkingDayAsDate();

    //then
    assertEquals(DateUtil.getDateFromLocalDate(lastFriday), previousWorkingDay);
}

@After
public void resetClock() {
    DateUtil.setClock(Clock.systemDefaultZone());
}
Andrew S
  • 2,509
  • 1
  • 12
  • 14
  • `Clock clock = monday.atStartOfDay().toInstant(ZoneId.systemDefault());` gives `toInstant (java.time.ZoneOffset) in ChronoLocalDateTime cannot be applied to (java.time.ZoneId)  ` – M06H Oct 03 '18 at 14:33
  • `Clock clock = Clock.fixed(monday.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault());` ? That would be correct assignment? https://docs.oracle.com/javase/8/docs/api/java/time/Clock.html#fixed-java.time.Instant-java.time.ZoneId- – M06H Oct 03 '18 at 14:38
  • I fixed the compile error - your variant would work too. – Andrew S Oct 03 '18 at 14:45
2

It's pretty simple. When you move the expected LocalDate initialization to after the mocking of the LocalDate.class, then the class is already mocked, Globally, as in every call to the class. This means means it doesn't give you the real java implementation. Which means you can't get the real java implementation of LocalDate.of(2017, 10, 13), because you overrode the class with a mocked version at this point.

Could the test be improved? Yes, don't use powermock.

It is considered bad practice to use powermock if you can avoid it. Its a long story, and I'm not sure this is the place to explain it all. The short version of it: if you didn't use power mock, you would have to make your code more flexible. Powermock allows you to cheat, and not make your code flexible. When you use power mock you don't get the same behavior during your test and your production code.

Read here for some more info: Why not PowerMock

Gonen I
  • 5,576
  • 1
  • 29
  • 60
1

Dates are always a pain when testing. But it can be fixed quite easily by using a DateSupplier.

A DateSupplier is a class you can inject everywhere there is a 'now' date created. Replace the date creation with the DateSupplier.get(). When testing, you just mock the DateSupplier and return the date you want.

An elaborate example can be found here: https://dzone.com/articles/mocking-jodatimes-datetime-and. (You don't really need Guava's Supplier)

In this case: inject a DateSupplier in your DateUtil class, and replace it with a mock during testing!

Tom Van Rossom
  • 1,440
  • 10
  • 18