java datetime mocking java-8 java-time

Burlarse de la API java.time de Java 8



datetime mocking (6)

Joda Time tiene un buen DateTimeUtils.setCurrentMillisFixed() para simular tiempo. Es muy práctico en las pruebas. ¿Hay un equivalente en la API java.time de Java 8 ?


Aquí hay una manera efectiva de anular el tiempo actual del sistema a una fecha específica para probar JUnit en una aplicación web Java 8 con EasyMock

Joda Time está muy bien (gracias Stephen, Brian, has hecho de nuestro mundo un lugar mejor) pero no pude usarlo.

Después de un poco de experimentación, finalmente se me ocurrió una forma de burlar el tiempo a una fecha específica en la API java.time de Java 8 con EasyMock

  • sin Joda Time API
  • y sin PowerMock.

Esto es lo que se debe hacer:

Lo que debe hacerse en la clase probada

Paso 1

Agregue un nuevo atributo java.time.Clock a la clase probada MyService y asegúrese de que el nuevo atributo se inicialice correctamente en los valores predeterminados con un bloque de creación de instancias 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 // (...) }

Paso 2

Inyecte el nuevo clock atributos en el método que requiere una fecha-hora actual. Por ejemplo, en mi caso tuve que verificar si una fecha almacenada en dataase sucedió antes de LocalDateTime.now() , que LocalDateTime.now(clock) 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(); } } // (...) }

Lo que debe hacerse en la clase de prueba

Paso 3

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, de esta forma:

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

Verifíquelo en modo de depuración y verá que la fecha del 3 de febrero de 2017 se ha inyectado correctamente en la instancia myService y se ha utilizado en las instrucciones de comparación, y luego se ha restablecido correctamente a la fecha actual con initDefaultClock() .


Este ejemplo incluso muestra cómo combinar InstantTime 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 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 } }


Usé un campo

private Clock clock;

y entonces

LocalDate.now(clock);

en mi código de producción Luego usé Mockito en mis pruebas de unidad 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)));


Clock.fixed una nueva clase para ocultar la creación Clock.fixed 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(); ... }


Me parece que utilizar Clock clutters es tu 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 pasado. Más que nada, el argumento me convenció de que no puedes paralizar las pruebas cuando te burlas de los métodos estáticos.

Sin embargo, puedes / debes seguir usando la burla estática si tienes que lidiar con el código heredado.


Lo más parecido es el objeto Clock . Puede crear un objeto Clock utilizando el tiempo que desee (o desde la hora actual del sistema). Todos los objetos date.time han sobrecargado now métodos que toman un objeto clock 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)) { ... } } }

Ver Clock JavaDoc para más detalles