Joda Time ha un bel DateTimeUtils.setCurrentMillisFixed () per simulare l'ora.
È molto pratico nei test.
Esiste un equivalente nell'API java.time di Java 8 ?
Joda Time ha un bel DateTimeUtils.setCurrentMillisFixed () per simulare l'ora.
È molto pratico nei test.
Esiste un equivalente nell'API java.time di Java 8 ?
Risposte:
La cosa più vicina è l' Clock
oggetto. È possibile creare un oggetto Orologio utilizzando l'ora che si desidera (o dall'ora corrente del sistema). Tutti gli oggetti date.time hanno now
metodi di overload che accettano invece un oggetto clock per l'ora corrente. Quindi puoi usare l'inserimento delle dipendenze per iniettare un orologio con un orario specifico:
public class MyBean {
private Clock clock; // dependency inject
...
public void process(LocalDate eventDate) {
if (eventDate.isBefore(LocalDate.now(clock)) {
...
}
}
}
Vedi Clock JavaDoc per maggiori dettagli
Ho usato una nuova classe per nascondere la Clock.fixed
creazione e semplificare i test:
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()
metodo e utilizzare direttamente il campo. Questo metodo non aggiunge altro che poche righe di codice.
Ho usato un campo
private Clock clock;
e poi
LocalDate.now(clock);
nel mio codice di produzione. Quindi ho usato Mockito nei miei unit test per simulare l'orologio usando Clock.fixed ():
@Mock
private Clock clock;
private Clock fixedClock;
Beffardo:
fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
doReturn(fixedClock.instant()).when(clock).instant();
doReturn(fixedClock.getZone()).when(clock).getZone();
Asserzione:
assertThat(expectedLocalDateTime, is(LocalDate.now(fixedClock)));
Trovo che l'uso di Clock
clutter nel codice di produzione.
È possibile utilizzare JMockit o PowerMock per simulare invocazioni di metodi statici nel codice di test. Esempio con 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 : Dopo aver letto i commenti sulla risposta di Jon Skeet a una domanda simile qui su SO, non sono d'accordo con me stesso. Più di ogni altra cosa l'argomento mi ha convinto che non puoi mettere in parallelo i test quando prendi in giro metodi statici.
Tuttavia, puoi / devi ancora usare la derisione statica se devi gestire il codice legacy.
Ho bisogno di LocalDate
istanza invece di LocalDateTime
.
Per questo motivo ho creato la seguente classe di utilità:
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);
}
}
E l'utilizzo per esso è come:
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());
}
}
Produzione:
2019-01-03
1998-12-12
2019-01-03
Sostituita tutta la creazione LocalDate.now()
in Clock.getCurrentDate()
nel progetto.
Perché è un'applicazione di avvio primaverile . Prima test
dell'esecuzione del profilo è sufficiente impostare una data predefinita per tutti i test:
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);
}
}
}
E aggiungi a spring.factories :
org.springframework.context.ApplicationListener = com.init.TestProfileConfigurer
Joda Time è sicuramente carino (grazie Stephen, Brian, avete reso il nostro mondo un posto migliore) ma non mi è stato permesso di usarlo.
Dopo alcuni esperimenti, alla fine ho escogitato un modo per simulare l'ora di una data specifica nell'API java.time di Java 8 con EasyMock
Ecco cosa è necessario fare:
Aggiungi un nuovo java.time.Clock
attributo alla classe testata MyService
e assicurati che il nuovo attributo verrà inizializzato correttamente ai valori predefiniti con un blocco di istanza o un costruttore:
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
// (...)
}
Iniettare il nuovo attributo clock
nel metodo che richiede una data-ora corrente. Ad esempio, nel mio caso ho dovuto controllare se una data memorizzata nel database è avvenuta prima LocalDateTime.now()
, che ho sostituito con LocalDateTime.now(clock)
, in questo modo:
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();
}
}
// (...)
}
Nella classe di test, crea un oggetto mock clock e iniettalo nell'istanza della classe testata appena prima di chiamare il metodo testato doExecute()
, quindi ripristinalo subito dopo, in questo modo:
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(..)
}
}
Controllalo in modalità debug e vedrai che la data del 3 febbraio 2017 è stata correttamente inserita myService
nell'istanza e utilizzata nelle istruzioni di confronto, quindi è stata correttamente reimpostata alla data corrente con initDefaultClock()
.
Questo esempio mostra anche come combinare Instant e LocalTime ( spiegazione dettagliata dei problemi con la conversione )
Una classe in prova
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
}
}
Con l'aiuto di PowerMockito per un test di avvio primaverile puoi deridere il file ZonedDateTime
. Hai bisogno di quanto segue.
Nella classe di test è necessario preparare il servizio che utilizza l'estensione ZonedDateTime
.
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({EscalationService.class})
@SpringBootTest
public class TestEscalationCases {
@Autowired
private EscalationService escalationService;
//...
}
Nel test è possibile preparare un tempo desiderato e ottenerlo in risposta alla chiamata al metodo.
@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
è utile nei test, mentreClock.system
oClock.systemUTC
potrebbe essere utilizzato nell'applicazione.