Joda Time tiene un buen DateTimeUtils.setCurrentMillisFixed () para simular el tiempo.
Es muy práctico en las pruebas.
¿Existe un equivalente en la API java.time de Java 8 ?
Joda Time tiene un buen DateTimeUtils.setCurrentMillisFixed () para simular el tiempo.
Es muy práctico en las pruebas.
¿Existe un equivalente en la API java.time de Java 8 ?
Respuestas:
Lo más cercano es el Clock
objeto. Puede crear un objeto Reloj utilizando la hora que desee (o desde la hora actual del Sistema). Todos los objetos date.time tienen now
métodos sobrecargados que toman un objeto de reloj en lugar de la hora actual. Entonces puede usar la inyección de dependencia para inyectar un Reloj con una hora específica:
public class MyBean {
private Clock clock; // dependency inject
...
public void process(LocalDate eventDate) {
if (eventDate.isBefore(LocalDate.now(clock)) {
...
}
}
}
Consulte Clock JavaDoc para obtener más detalles.
Usé una nueva clase para ocultar la Clock.fixed
creación y simplificar las pruebas:
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()
método y usar el campo directamente. Este método no agrega nada más que unas pocas líneas de código.
Usé un campo
private Clock clock;
y entonces
LocalDate.now(clock);
en mi código de producción. Luego usé Mockito en mis pruebas unitarias para simular el Reloj usando Clock.fixed ():
@Mock
private Clock clock;
private Clock fixedClock;
Burlón:
fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
doReturn(fixedClock.instant()).when(clock).instant();
doReturn(fixedClock.getZone()).when(clock).getZone();
Afirmación:
assertThat(expectedLocalDateTime, is(LocalDate.now(fixedClock)));
Encuentro el uso de Clock
desorden en su código de producción.
Puede usar JMockit o PowerMock para simular invocaciones de métodos estáticos en su código de prueba. Ejemplo 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);
}
EDITAR : Después de leer los comentarios sobre la respuesta de Jon Skeet a una pregunta similar aquí en SO , no estoy de acuerdo con mi yo pasado. Más que cualquier otra cosa, el argumento me convenció de que no se pueden paralizar las pruebas cuando se simulan métodos estáticos.
Sin embargo, puede / debe usar la simulación estática si tiene que lidiar con el código heredado.
Necesito LocalDate
instancia en lugar de LocalDateTime
.
Con tal razón creé la siguiente clase de utilidad:
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);
}
}
Y su uso es como:
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());
}
}
Salida:
2019-01-03
1998-12-12
2019-01-03
Reemplazado toda la creación LocalDate.now()
que Clock.getCurrentDate()
en el proyecto.
Porque es una aplicación de arranque de primavera . Antes de la test
ejecución del perfil, simplemente establezca una fecha predefinida para todas las pruebas:
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);
}
}
}
Y agregue a spring.factories :
org.springframework.context.ApplicationListener = com.init.TestProfileConfigurer
Joda Time es realmente agradable (gracias Stephen, Brian, han hecho de nuestro mundo un lugar mejor) pero no se me permitió usarlo.
Después de experimentar un poco, finalmente se me ocurrió una forma de simular el tiempo para una fecha específica en la API java.time de Java 8 con EasyMock
Esto es lo que debe hacerse:
Agregue un nuevo java.time.Clock
atributo a la clase probada MyService
y asegúrese de que el nuevo atributo se inicialice correctamente en los valores predeterminados con un bloque de instanciación o un constructor:
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
// (...)
}
Inyecte el nuevo atributo clock
en el método que requiere una fecha y hora actual. Por ejemplo, en mi caso tuve que realizar una verificación de si una fecha almacenada en la base de datos sucedió antes LocalDateTime.now()
, que reemplacé con LocalDateTime.now(clock)
, así:
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();
}
}
// (...)
}
En la clase de prueba, cree un objeto de reloj simulado e inyéctelo en la instancia de la clase probada justo antes de llamar al método probado doExecute()
, luego reinícielo inmediatamente después, así:
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(..)
}
}
Compruébelo en modo de depuración y verá que la fecha del 3 de febrero de 2017 se ha inyectado correctamente en la myService
instancia y se ha utilizado en la instrucción de comparación, y luego se ha restablecido correctamente a la fecha actual con initDefaultClock()
.
Este ejemplo incluso muestra cómo combinar Instant y LocalTime ( explicación detallada de los problemas con la conversión )
Una clase bajo prueba
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);
}
}
Una prueba de 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 la ayuda de PowerMockito para una prueba de arranque de primavera, puede burlarse del archivo ZonedDateTime
. Necesita lo siguiente.
En la clase de prueba, debe preparar el servicio que utiliza el ZonedDateTime
.
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringRunner.class)
@PrepareForTest({EscalationService.class})
@SpringBootTest
public class TestEscalationCases {
@Autowired
private EscalationService escalationService;
//...
}
En la prueba, puede preparar un tiempo deseado y obtenerlo en respuesta a la llamada al método.
@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
es útil en las pruebas, mientras queClock.system
oClock.systemUTC
podría usarse en la aplicación.