Working with code R031

R031 - Method may be time-sensitive

Java 8 has the addition of the Clock, which can be used as a time provider for effective stubbing when test writing.

Output code R031 means that Diffblue Cover was only able to write tests that are time-sensitive. This may mean that the tests do not run at an alternative time. Using a java.util.Clock instance allows the time to be parameterized during testing, as shown in the example below:

Consider the following example of a class modelling the loan of a library book, including an isOverdue() method to workout whether the book needs to be returned:

package com.example;

import java.time.LocalDate;
import java.util.Objects;

public class LibraryBookLoan {

    public final String bookName;
    public final LocalDate startDate;

    public LibraryBookLoan(String bookName, LocalDate startDate) {
        this.bookName = Objects.requireNonNull(bookName);
        this.startDate = Objects.requireNonNull(startDate);
    }

    public LocalDate getDueDate() {
        return startDate.plusDays(10);
    }

    public boolean isOverdue() {
        final LocalDate today = LocalDate.now();
        return today.isAfter(getDueDate());
    }
}

When Diffblue Cover writes tests for this class you can see that we get a R031 indicating that the isOverdue() method is time sensitive, making it difficult to test.

Creating tests:
---------------
    [1/3] com.example.LibraryBookLoan.<init>
    [1/3]   Complete tests created: 1
    [2/3] com.example.LibraryBookLoan.getDueDate
    [2/3]   Complete tests created: 1
    [3/3] com.example.LibraryBookLoan.isOverdue
    [3/3]   R031: Method may be time-sensitive
    [3/3]     Diffblue Cover was only able to write assertions which were time-sensitive.
    [3/3]     The assertions no longer passed when run at an alternate date, time and
    [3/3]     timezone. Try refactoring the method to take a java.util.Clock instance so
    [3/3]     that the time can be parameterized during testing.

The recommendation here is to refactor the isOverdue() method to take a java.time.Clock parameter:

package com.example;

import java.time.Clock;
import java.time.LocalDate;

public class LibraryBookLoan {

    public final String bookName;
    public final LocalDate startDate;

    public LibraryBookLoan(String bookName, LocalDate startDate) {
        this.bookName = bookName;
        this.startDate = startDate;
    }

    public LocalDate getDueDate() {
        return startDate.plusDays(10);
    }

    public boolean isOverdue(Clock clock) {
        final LocalDate today = LocalDate.now(clock);
        return today.isAfter(getDueDate());
    }
}

Now we get tests written:

  @Test
  void testIsOverdue() {
    // Arrange
    LibraryBookLoan libraryBookLoan = new LibraryBookLoan("Book Name", LocalDate.ofEpochDay(1L));
    LocalDateTime atStartOfDayResult = LocalDate.of(1970, 1, 1).atStartOfDay();
    ZoneId ofResult = ZoneId.of("UTC");

    // Act and Assert
    assertFalse(libraryBookLoan.isOverdue(Clock.fixed(atStartOfDayResult.atZone(ofResult).toInstant(), ofResult)));
  }

  @Test
  void testIsOverdue2() {
    // Arrange
    LibraryBookLoan libraryBookLoan = new LibraryBookLoan("Book Name", LocalDate.ofYearDay(10, 10));
    LocalDateTime atStartOfDayResult = LocalDate.of(1970, 1, 1).atStartOfDay();
    ZoneId ofResult = ZoneId.of("UTC");

    // Act and Assert
    assertTrue(libraryBookLoan.isOverdue(Clock.fixed(atStartOfDayResult.atZone(ofResult).toInstant(), ofResult)));
  }

Sometimes it's not acceptable to change all callers, and the old interface needs to be maintained. If that's the case then the original time-sensitive signature can be maintained and a parameterized one can be added:

package com.example;

import java.time.Clock;
import java.time.LocalDate;

public class LibraryBookLoan {

    public final String bookName;
    public final LocalDate startDate;

    public LibraryBookLoan(String bookName, LocalDate startDate) {
        this.bookName = bookName;
        this.startDate = startDate;
    }

    public LocalDate getDueDate() {
        return startDate.plusDays(10);
    }

    public boolean isOverdue() {
        return isOverdue(Clock.systemDefaultZone());
    }

    boolean isOverdue(Clock clock) {
        final LocalDate today = LocalDate.now(clock);
        return today.isAfter(getDueDate());
    }

}

This way you still get the R031 output codes for the isOverdue() method, but the underlying logic can be tested via the isOverdue(Clock) method.

Last updated