web-dev-qa-db-fra.com

Temps moqueurs dans l'API Java.time de Java 8

Joda Time a un Nice DateTimeUtils.setCurrentMillisFixed () pour simuler l'heure. 

C'est très pratique dans les tests. 

Existe-t-il un équivalent dans L'API Java.time de Java 8 ?

70
neu242

La chose la plus proche est l'objet Clock. Vous pouvez créer un objet Clock à tout moment (ou à partir de l'heure actuelle du système). Tous les objets date.time ont surchargé des méthodes now qui prennent un objet d'horloge à la place pour l'heure actuelle. Vous pouvez donc utiliser l'injection de dépendance pour injecter une horloge avec une heure spécifique:

public class MyBean {
    private Clock clock;  // dependency inject
    ...
    public void process(LocalDate eventDate) {
      if (eventDate.isBefore(LocalDate.now(clock)) {
        ...
      }
    }
  }

Voir Clock JavaDoc pour plus de détails

57
dkatzel

J'ai utilisé une nouvelle classe pour masquer la création Clock.fixed et simplifier les tests:

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

Je trouve que Clock encombre votre code de production.

Vous pouvez utiliser JMockit ou PowerMock pour simuler des appels de méthode statiques dans votre code de test . Exemple avec 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: Après avoir lu les commentaires sur la réponse de Jon Skeet à une question similaire ici sur SO je ne suis pas d'accord avec mon passé. Plus que tout, l'argument m'a convaincu que vous ne pouvez pas mettre en parallèle des tests lorsque vous vous moquez de méthodes statiques.

Vous pouvez/devez toujours utiliser le moquage statique si vous devez gérer du code hérité, cependant.

7
Stefan Haberl

J'ai utilisé un champ

private Clock clock;

et alors

LocalDate.now(clock);

dans mon code de production. Ensuite, j'ai utilisé Mockito dans mes tests unitaires pour simuler l'horloge avec Clock.fixed ():

@Mock
private Clock clock;
private Clock fixedClock;

Railleur:

fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
doReturn(fixedClock.instant()).when(clock).instant();
doReturn(fixedClock.getZone()).when(clock).getZone();

Affirmation:

assertThat(expectedLocalDateTime, is(LocalDate.now(fixedClock)));
5
Claas Wilke

Voici un moyen efficace de remplacer l'heure système actuelle par une date spécifique à des fins de test JUnit dans une application Web Java 8 avec EasyMock

Joda Time est sûr Nice (merci Stephen, Brian, vous avez rendu notre monde meilleur) mais je ne pouvais pas l'utiliser. 

Après quelques expériences, j'ai finalement trouvé un moyen de simuler l'heure à une date spécifique dans l'API Java.time de Java 8 avec EasyMock.

  • sans l'API Joda Time
  • et sans PowerMock.

Voici ce qu'il faut faire:

Ce qui doit être fait dans la classe testée

Étape 1

Ajoutez un nouvel attribut Java.time.Clock à la classe testée MyService et assurez-vous que le nouvel attribut sera initialisé correctement aux valeurs par défaut avec un bloc d'instanciation ou un constructeur:

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

Étape 2

Injectez le nouvel attribut clock dans la méthode qui appelle une date/heure actuelle. Par exemple, dans mon cas, je devais vérifier si une date stockée dans la base de données était antérieure à LocalDateTime.now(), que j'ai remplacée par LocalDateTime.now(clock), comme ceci:

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

Ce qui doit être fait dans la classe de test

Étape 3

Dans la classe de test, créez un objet d'horloge factice et insérez-le dans l'instance de la classe testée juste avant d'appeler la méthode testée doExecute(), puis réinitialisez-le juste après, comme suit:

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

Vérifiez-le en mode débogage et vous verrez que la date du 3 février 2017 a été correctement injectée dans l'instance myService et utilisée dans l'instruction de comparaison, puis a été réinitialisée correctement à la date du jour avec initDefaultClock().

1
KiriSakow

Cet exemple montre même comment combiner Instant et LocalTime ( explication détaillée des problèmes liés à la conversion )

Une classe à l'essai

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

}

Un test Groovy

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

Avec l’aide de PowerMockito pour un test de démarrage de printemps, vous pouvez vous moquer de ZonedDateTime. Vous avez besoin de ce qui suit.

Annotations

Sur la classe de test vous devez préparer le service qui utilise le ZonedDateTime.

@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({EscalationService.class})
@SpringBootTest
public class TestEscalationCases {
  @Autowired
  private EscalationService escalationService;
  //...
}

Cas de test

Dans le test, vous pouvez préparer l'heure désirée et l'obtenir en réponse à l'appel de la méthode.

  @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

J'ai besoin de LocalDate instance au lieu de LocalDateTime.
C’est pour cette raison que j’ai créé la classe d’utilité suivante:

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

Et son utilisation ressemble à:

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

Sortie:

2019-01-03
1998-12-12
2019-01-03

Remplacé toutes les créations LocalDate.now() par Clock.getCurrentDate() dans le projet. 

Parce que c'estspring bootapplication. Avant l'exécution du profil test, définissez simplement une date prédéfinie pour tous les 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);
        }
    }
}

Et ajoutez à spring.factories:

org.springframework.context.ApplicationListener = com.init.TestProfileConfigurer

0
nazar_art