Joda Time hat ein schönes DateTimeUtils.setCurrentMillisFixed () , um die Zeit zu verspotten.
Es ist sehr praktisch in Tests.
Gibt es eine Entsprechung in der java.time-API von Java 8 ?
Joda Time hat ein schönes DateTimeUtils.setCurrentMillisFixed () , um die Zeit zu verspotten.
Es ist sehr praktisch in Tests.
Gibt es eine Entsprechung in der java.time-API von Java 8 ?
Antworten:
Das nächste ist das Clock
Objekt. Sie können ein Clock-Objekt jederzeit (oder ab der aktuellen Systemzeit) erstellen. Alle date.time-Objekte verfügen über überladene now
Methoden, die stattdessen ein Uhrobjekt für die aktuelle Uhrzeit verwenden. Sie können also 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 auszublenden 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();
...
}
getClock()
Methode auch entfernen und das Feld direkt verwenden. Diese Methode fügt nur ein paar Codezeilen hinzu.
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 Uhr 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)));
Ich finde mit Clock
Unordnung Ihren Produktionscode.
Sie können JMockit oder PowerMock verwenden , um statische Methodenaufrufe in Ihrem Testcode zu verspotten. 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 auf SO gelesen habe, bin ich nicht mit meinem früheren Ich einverstanden. Das Argument hat mich vor allem davon überzeugt, dass Sie Tests nicht paralleln können, wenn Sie statische Methoden verspotten.
Sie können / müssen jedoch statisches Mocking verwenden, wenn Sie sich mit Legacy-Code befassen müssen.
Ich brauche LocalDate
Instanz statt 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
Ersetzt die gesamte Schöpfung LocalDate.now()
bis Clock.getCurrentDate()
in Projekt.
Weil es eine Spring Boot- Anwendung ist. Vor dem test
Profil Ausführung gesetzt nur einen vordefinierten Zeitpunkt für alle Tests:
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
Joda Time ist sicher schön (danke Stephen, Brian, du hast unsere Welt zu einem besseren Ort gemacht), aber ich durfte sie nicht benutzen.
Nach einigen Experimenten fand ich schließlich eine Möglichkeit, die Zeit bis zu einem bestimmten Datum in der java.time-API von Java 8 mit EasyMock zu verspotten
Folgendes muss getan werden:
Fügen Sie java.time.Clock
der getesteten Klasse ein neues Attribut hinzu MyService
und stellen Sie sicher, dass das neue Attribut bei Standardwerten mit einem Instanziierungsblock oder einem Konstruktor 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 anfordert. In meinem Fall musste ich beispielsweise überprüfen, ob ein in der Datenbank gespeichertes Datum zuvor aufgetreten ist LocalDateTime.now()
, das ich LocalDateTime.now(clock)
wie folgt 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, fügen Sie es in die Instanz der getesteten Klasse ein, bevor Sie die getestete Methode aufrufen doExecute()
, und setzen Sie es 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(..)
}
}
Überprüfen Sie dies im Debug-Modus, und Sie werden sehen, dass das Datum vom 3. Februar 2017 korrekt in die myService
Instanz eingefügt und in der Vergleichsanweisung verwendet wurde und dann ordnungsgemäß auf das aktuelle Datum mit zurückgesetzt wurde initDefaultClock()
.
Dieses Beispiel zeigt sogar, wie Instant und LocalTime kombiniert werden ( detaillierte Erläuterung 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 Groovy-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 das verspotten ZonedDateTime
. Sie benötigen Folgendes.
In der Testklasse müssen Sie den Dienst vorbereiten, der das verwendet ZonedDateTime
.
@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 abrufen.
@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
}
Clock.fixed
beim Testen nützlich, währendClock.system
oderClock.systemUTC
möglicherweise in der Anwendung verwendet wird.