web-dev-qa-db-ger.com

Verspottungszeit in Java.time API von Java 8

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 ?

70
neu242

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

57
dkatzel

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();

    ...
}
19
LuizSignorelli

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.

7
Stefan Haberl

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)));
5
Claas Wilke

Im Folgenden wird beschrieben, wie Sie die aktuelle Systemzeit zu einem bestimmten Datum für JUnit-Testzwecke in einer Java 8-Webanwendung mit EasyMock überschreiben können

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

  • ohne Joda Time API
  • und ohne PowerMock.

Folgendes muss getan werden:

Was muss in der getesteten Klasse gemacht werden?

Schritt 1

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
  // (...)
}

Schritt 2

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();
    }
  }
  // (...) 
}

Was muss in der Testklasse gemacht werden?

Schritt 3

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.

1
KiriSakow

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
    }

}
0
banterCZ

Mit Hilfe von PowerMockito für einen Spring Boot-Test können Sie den ZonedDateTimeverspotten. Du brauchst folgendes.

Anmerkungen

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;
  //...
}

Testfall

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 
}
0
pasquale

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

0
nazar_art