Avoid LocalDate.now()

Java 8 introduced new APIs for Date and Time to improve on the previous java.util.Date and java.util.Calendar. These new features are very useful and make the date or time manipulation very easy.

The now() problem.

Consider the following scenario, we have a Student entity representing students joining a school. We want to find all the students before a given date. See below:

@Entity
@Getter
@Setter
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name; 
    private LocalDate dateJoined;   
}
// The repository
@Repository
 public interface StudentRepository extends JpaRepository {
     /**
      * Find all Students before a given date
      */
     public List findByDateJoinedLessThan(LocalDate date);
 }
// The service
@RequiredArgsConstructor
@Service
public class StudentService {
    private final StudentRepository studentRepository;
    /**
     * Find all Students before current date
    */
    public List<Student> findStudentsBeforeCurrentDate() {
        return studentRepository.findByDateJoinedLessThan(LocalDate.now());
    }       
}

Notice the LocalDate.now() in our function.

  • Assume that the current date is 2020-10-31
  • How are we going to test the function SystemService.findStudentsBeforeCurrentDate() ?
  • Not easy as we cannot mock LocalDate class nor the now() function as they are final and static respectively.
  • We would have to use reflection or a util like powerMock.
INSERT INTO student(id, name, dateJoined) VALUES(1, 'John Doe', '2019-01-01');
INSERT INTO student(id, name, dateJoined) VALUES(2, 'Sam Jones', '2020-01-01');
INSERT INTO student(id, name, dateJoined) VALUES(3, 'Sarah Harris', '2021-01-01');
@ExtendWith({SpringExtension.class})
@DataJpaTest
class StudentServiceTest {
     @Autowired
     private StudentRepository studentRepository ;
     @Test 
     void testFindStudentsBeforeCurrentDate() {     
        StudentService service = new StudentService(studentRepository);     
        List<Student> students = service.findStudentsBeforeCurrentDate();     
        assertThat("2 students should be available", students, hasSize(2)); 
     }
}

This test method will run successfully for the rest of the year 2020 as we have two students who joined on the respective dates 2019-01-01 and 2020-01-01.

However the test will fail on the 2021-01-02 as the service.findStudentsBeforeCurrentDate() will now return three students including the student who joined on 2021-01-01.

The solution

In our code we have to replace LocalDate.now() with LocalDate.now(clock). We can then pass Clock.systemDefaultZone() for production and another fixed clock for testing. See below example:

@Bean 
public Clock clock() {     
  return Clock.systemDefaultZone(); 
}
@RequiredArgsConstructor
@Service
public class StudentService {
    private final StudentRepository studentRepository;
    private final Clock clock;
    /**
     * Find all Students before current date
    */
    public List<Student> findStudentsBeforeCurrentDate() {
        return studentRepository.findByDateJoinedLessThan(LocalDate.now(clock));
    }       
}
@ExtendWith({SpringExtension.class})
@DataJpaTest
class StudentServiceTest {
     // Fixed current date to make our tests
     private final static LocalDate DATE = LocalDate.of(2020, 12, 31);
     @Autowired
     private StudentRepository studentRepository ;
     @Test 
     void testFindStudentsBeforeCurrentDate() {
        Clock clock = Clock.fixed(DATE.atStartOfDay(ZoneId.systemDefault()).toInstant(),ZoneId.systemDefault());
        StudentService service = new StudentService(studentRepository,clock);     
        List<Student> students = service.findStudentsBeforeCurrentDate();     
        assertThat("2 students should be available", students, hasSize(2)); 
     }
}
@ExtendWith({SpringExtension.class})
@DataJpaTest
class StudentServiceTest {
     // Fixed current date to make our tests
     private final static LocalDate DATE = LocalDate.of(2020, 12, 31);
     @Autowired
     private StudentRepository studentRepository ;
     @Test 
     void testFindStudentsBeforeCurrentDate() {
        Clock clock = Clock.fixed(DATE.atStartOfDay(ZoneId.systemDefault()).toInstant(),ZoneId.systemDefault());
        StudentService service = new StudentService(studentRepository,clock);     
        List<Student> students = service.findStudentsBeforeCurrentDate();     
        assertThat("2 students should be available", students, hasSize(2)); 
     }
}

Leave a Reply