Joda Time hat eine Nice DateTimeUtils.setCurrentMillisFixed () - Zeit, um die Zeit zu messen.
Es ist sehr praktisch in Tests.
Gibt es ein Äquivalent in der Java.time-API von Java 8 ?
Am nächsten ist das Objekt Clock
. Sie können ein Uhrobjekt zu jeder Zeit erstellen (oder von der aktuellen Uhrzeit des Systems). Alle date.time-Objekte haben now
-Methoden überladen, die stattdessen ein Uhrobjekt für die aktuelle Uhrzeit verwenden. So können Sie die Abhängigkeitsinjektion verwenden, um eine Uhr mit einer bestimmten Zeit zu injizieren:
public class MyBean {
private Clock clock; // dependency inject
...
public void process(LocalDate eventDate) {
if (eventDate.isBefore(LocalDate.now(clock)) {
...
}
}
}
Siehe Clock JavaDoc für weitere Details
Ich habe eine neue Klasse verwendet, um die Clock.fixed
-Erstellung zu verbergen und die Tests zu vereinfachen:
public class TimeMachine {
private static Clock clock = Clock.systemDefaultZone();
private static ZoneId zoneId = ZoneId.systemDefault();
public static LocalDateTime now() {
return LocalDateTime.now(getClock());
}
public static void useFixedClockAt(LocalDateTime date){
clock = Clock.fixed(date.atZone(zoneId).toInstant(), zoneId);
}
public static void useSystemDefaultZoneClock(){
clock = Clock.systemDefaultZone();
}
private static Clock getClock() {
return clock ;
}
}
public class MyClass {
public void doSomethingWithTime() {
LocalDateTime now = TimeMachine.now();
...
}
}
@Test
public void test() {
LocalDateTime twoWeeksAgo = LocalDateTime.now().minusWeeks(2);
MyClass myClass = new MyClass();
TimeMachine.useFixedClockAt(twoWeeksAgo);
myClass.doSomethingWithTime();
TimeMachine.useSystemDefaultZoneClock();
myClass.doSomethingWithTime();
...
}
Ich finde, dass Sie mit Clock
Ihren Produktionscode überladen.
Sie können JMockit oder PowerMock verwenden, um statische Methodenaufrufe in Ihrem Testcode zu simulieren. __ Beispiel mit JMockit:
@Test
public void testSth() {
LocalDate today = LocalDate.of(2000, 6, 1);
new Expectations(LocalDate.class) {{
LocalDate.now(); result = today;
}};
Assert.assertEquals(LocalDate.now(), today);
}
EDIT: Nachdem ich die Kommentare zu Jon Skeets Antwort auf eine ähnliche -Frage hier zu SO gelesen habe, kann ich meinem früheren Ich nicht zustimmen. Das Argument hat mich vor allem überzeugt, dass man Tests nicht parallelisieren kann, wenn man sich mit statischen Methoden beschäftigt.
Sie können/müssen jedoch immer noch statisches Mocking verwenden, wenn Sie mit älterem Code umgehen müssen.
Ich habe ein Feld benutzt
private Clock clock;
und dann
LocalDate.now(clock);
in meinem Produktionscode. Dann habe ich Mockito in meinen Unit-Tests verwendet, um die Clock mit Clock.fixed () zu verspotten:
@Mock
private Clock clock;
private Clock fixedClock;
Verspottung:
fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
doReturn(fixedClock.instant()).when(clock).instant();
doReturn(fixedClock.getZone()).when(clock).getZone();
Behauptung:
assertThat(expectedLocalDateTime, is(LocalDate.now(fixedClock)));
Joda Time ist sicher Nizza (danke Stephen, Brian, du hast unsere Welt zu einem besseren Ort gemacht), aber ich durfte sie nicht benutzen.
Nach einigem Experimentieren fand ich schließlich eine Möglichkeit, die Zeit auf ein bestimmtes Datum in der Java.time-API von Java 8 mit EasyMock zu simulieren
Folgendes muss getan werden:
Fügen Sie der getesteten Klasse MyService
ein neues Java.time.Clock
-Attribut hinzu, und stellen Sie sicher, dass das neue Attribut mit einem Instantiierungsblock oder einem Konstruktor mit Standardwerten ordnungsgemäß initialisiert wird:
import Java.time.Clock;
import Java.time.LocalDateTime;
public class MyService {
// (...)
private Clock clock;
public Clock getClock() { return clock; }
public void setClock(Clock newClock) { clock = newClock; }
public void initDefaultClock() {
setClock(
Clock.system(
Clock.systemDefaultZone().getZone()
// You can just as well use
// Java.util.TimeZone.getDefault().toZoneId() instead
)
);
}
{ initDefaultClock(); } // initialisation in an instantiation block, but
// it can be done in a constructor just as well
// (...)
}
Fügen Sie das neue Attribut clock
in die Methode ein, die ein aktuelles Datum und eine aktuelle Uhrzeit erfordert. In meinem Fall musste ich zum Beispiel überprüfen, ob ein in der Datenbank gespeichertes Datum vor LocalDateTime.now()
aufgetreten ist, das ich mit LocalDateTime.now(clock)
ersetzt habe.
import Java.time.Clock;
import Java.time.LocalDateTime;
public class MyService {
// (...)
protected void doExecute() {
LocalDateTime dateToBeCompared = someLogic.whichReturns().aDate().fromDB();
while (dateToBeCompared.isBefore(LocalDateTime.now(clock))) {
someOtherLogic();
}
}
// (...)
}
Erstellen Sie in der Testklasse ein Mock-Clock-Objekt, und fügen Sie es in die Instanz der getesteten Klasse ein, bevor Sie die getestete Methode doExecute()
aufrufen, und setzen Sie sie anschließend wie folgt zurück:
import Java.time.Clock;
import Java.time.LocalDateTime;
import Java.time.OffsetDateTime;
import org.junit.Test;
public class MyServiceTest {
// (...)
private int year = 2017; // Be this a specific
private int month = 2; // date we need
private int day = 3; // to simulate.
@Test
public void doExecuteTest() throws Exception {
// (...) EasyMock stuff like mock(..), expect(..), replay(..) and whatnot
MyService myService = new MyService();
Clock mockClock =
Clock.fixed(
LocalDateTime.of(year, month, day, 0, 0).toInstant(OffsetDateTime.now().getOffset()),
Clock.systemDefaultZone().getZone() // or Java.util.TimeZone.getDefault().toZoneId()
);
myService.setClock(mockClock); // set it before calling the tested method
myService.doExecute(); // calling tested method
myService.initDefaultClock(); // reset the clock to default right afterwards with our own previously created method
// (...) remaining EasyMock stuff: verify(..) and assertEquals(..)
}
}
Wenn Sie es im Debug-Modus überprüfen, wird das Datum von 2017 Feb 3 korrekt in die Variable myService
eingefügt und in der Vergleichsanweisung verwendet. Anschließend wurde es mit initDefaultClock()
ordnungsgemäß auf das aktuelle Datum zurückgesetzt.
Dieses Beispiel zeigt sogar, wie Sie Instant und LocalTime kombinieren können ( detaillierte Erklärung der Probleme bei der Konvertierung ).
Eine Klasse im Test
import Java.time.Clock;
import Java.time.LocalTime;
public class TimeMachine {
private LocalTime from = LocalTime.MIDNIGHT;
private LocalTime until = LocalTime.of(6, 0);
private Clock clock = Clock.systemDefaultZone();
public boolean isInInterval() {
LocalTime now = LocalTime.now(clock);
return now.isAfter(from) && now.isBefore(until);
}
}
Ein grooviger Test
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import Java.time.Clock
import Java.time.Instant
import static Java.time.ZoneOffset.UTC
import static org.junit.runners.Parameterized.Parameters
@RunWith(Parameterized)
class TimeMachineTest {
@Parameters(name = "{0} - {2}")
static data() {
[
["01:22:00", true, "in interval"],
["23:59:59", false, "before"],
["06:01:00", false, "after"],
]*.toArray()
}
String time
boolean expected
TimeMachineTest(String time, boolean expected, String testName) {
this.time = time
this.expected = expected
}
@Test
void test() {
TimeMachine timeMachine = new TimeMachine()
timeMachine.clock = Clock.fixed(Instant.parse("2010-01-01T${time}Z"), UTC)
def result = timeMachine.isInInterval()
assert result == expected
}
}
Mit Hilfe von PowerMockito für einen Spring Boot-Test können Sie den ZonedDateTime
verspotten. Du brauchst folgendes.
In der Testklasse Sie müssen den Dienst vorbereiten wird der Name der ZonedDateTime
verwendet.
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({EscalationService.class})
@SpringBootTest
public class TestEscalationCases {
@Autowired
private EscalationService escalationService;
//...
}
Im Test können Sie eine gewünschte Zeit vorbereiten und als Antwort auf den Methodenaufruf erhalten.
@Test
public void escalateOnMondayAt14() throws Exception {
ZonedDateTime preparedTime = ZonedDateTime.now();
preparedTime = preparedTime.with(DayOfWeek.MONDAY);
preparedTime = preparedTime.withHour(14);
PowerMockito.mockStatic(ZonedDateTime.class);
PowerMockito.when(ZonedDateTime.now(ArgumentMatchers.any(ZoneId.class))).thenReturn(preparedTime);
// ... Assertions
}
Ich brauche LocalDate
Instanz anstelle von LocalDateTime
.
Aus diesem Grund habe ich folgende Dienstprogrammklasse erstellt:
public final class Clock {
private static long time;
private Clock() {
}
public static void setCurrentDate(LocalDate date) {
Clock.time = date.toEpochDay();
}
public static LocalDate getCurrentDate() {
return LocalDate.ofEpochDay(getDateMillis());
}
public static void resetDate() {
Clock.time = 0;
}
private static long getDateMillis() {
return (time == 0 ? LocalDate.now().toEpochDay() : time);
}
}
Und die Verwendung dafür ist wie folgt:
class ClockDemo {
public static void main(String[] args) {
System.out.println(Clock.getCurrentDate());
Clock.setCurrentDate(LocalDate.of(1998, 12, 12));
System.out.println(Clock.getCurrentDate());
Clock.resetDate();
System.out.println(Clock.getCurrentDate());
}
}
Ausgabe:
2019-01-03
1998-12-12
2019-01-03
Die gesamte Erstellung LocalDate.now()
in Clock.getCurrentDate()
im Projekt ersetzt.
Weil esspring bootapplication ist. Vor der Ausführung von test
muss für alle Tests ein vordefiniertes Datum festgelegt werden:
public class TestProfileConfigurer implements ApplicationListener<ApplicationPreparedEvent> {
private static final LocalDate TEST_DATE_MOCK = LocalDate.of(...);
@Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
if (environment.acceptsProfiles(Profiles.of("test"))) {
Clock.setCurrentDate(TEST_DATE_MOCK);
}
}
}
Und zu spring.factories
hinzufügen:
org.springframework.context.ApplicationListener = com.init.TestProfileConfigurer