Pruebas unitarias dependientes del tiempo

Necesito probar una función cuyo resultado dependerá de la hora actual (usando el tiempo de Joda isBeforeNow() ).

  public boolean isAvailable() { return (this.someDate.isBeforeNow()); } 

¿Es posible omitir / simular el tiempo del sistema con Mockito para que pueda probar la función de manera confiable?

Joda time admite establecer una hora actual “falsa” a través de los métodos setCurrentMillisFixed y setCurrentMillisOffset de la clase DateTimeUtils .

Ver http://joda-time.sourceforge.net/api-release/org/joda/time/DateTimeUtils.html

La mejor forma (IMO) de hacer que tu código sea comprobable es extraer la dependencia de “cuál es la hora actual” en su propia interfaz, con una implementación que usa la hora actual del sistema (usada normalmente) y una implementación que te permite establecer el tiempo , avanza como quieras, etc.

He utilizado este enfoque en diversas situaciones, y funcionó bien. Es fácil de configurar: solo cree una interfaz (por ejemplo, Clock ) que tenga un único método para darle el instante actual en el formato que desee (por ejemplo, usando Joda Time o posiblemente una Date ).

Java 8 introdujo la clase abstracta java.time.Clock que le permite tener una implementación alternativa para las pruebas. Esto es exactamente lo que Jon sugirió en su respuesta en ese momento.

Para agregar a la respuesta de Jon Skeet , Joda Time ya contiene una interfaz de tiempo actual: DateTimeUtils.MillisProvider

Por ejemplo:

 import org.joda.time.DateTime; import org.joda.time.DateTimeUtils.MillisProvider; public class Check { private final MillisProvider millisProvider; private final DateTime someDate; public Check(MillisProvider millisProvider, DateTime someDate) { this.millisProvider = millisProvider; this.someDate = someDate; } public boolean isAvailable() { long now = millisProvider.getMillis(); return (someDate.isBefore(now)); } } 

Simula el tiempo en una prueba unitaria (usando Mockito, pero podrías implementar tu propia clase MillisProviderMock):

 DateTime fakeNow = new DateTime(2016, DateTimeConstants.MARCH, 28, 9, 10); MillisProvider mockMillisProvider = mock(MillisProvider.class); when(mockMillisProvider.getMillis()).thenReturn(fakeNow.getMillis()); Check check = new Check(mockMillisProvider, someDate); 

Use la hora actual en producción ( DateTimeUtils.SYSTEM_MILLIS_PROVIDER se agregó a Joda Time en 2.9.3):

 Check check = new Check(DateTimeUtils.SYSTEM_MILLIS_PROVIDER, someDate); 

Utilizo un enfoque similar al de Jon, pero en lugar de crear una interfaz especializada solo para la hora actual (por ejemplo, Clock ), suelo crear una interfaz de prueba especial (por ejemplo, MockupFactory ). Puse todos los métodos que necesito para probar el código. Por ejemplo, en uno de mis proyectos tengo cuatro métodos allí:

  • uno que devuelve un cliente de base de datos de maqueta;
  • uno que crea un objeto notificador de maqueta que notifica al código sobre los cambios en la base de datos;
  • uno que crea una maqueta java.util.Timer que ejecuta las tareas cuando quiero;
  • uno que devuelve la hora actual.

La clase que se prueba tiene un constructor que acepta esta interfaz entre otros argumentos. El que no tiene este argumento solo crea una instancia predeterminada de esta interfaz que funciona “en la vida real”. Tanto la interfaz como el constructor son paquetes privados, por lo que la API de prueba no se filtra fuera del paquete.

Si necesito más objetos imitados, simplemente agrego un método a esa interfaz y lo implemento tanto en pruebas como en implementaciones reales.

De esta manera, diseñé un código adecuado para las pruebas en primer lugar sin imponer demasiado en el código en sí. De hecho, el código se vuelve aún más limpio de esta manera ya que muchos códigos de fábrica se reúnen en un solo lugar. Por ejemplo, si necesito cambiar a otra implementación de cliente de base de datos en código real, solo tengo que modificar una sola línea en lugar de buscar referencias al constructor.

Por supuesto, al igual que en el caso del enfoque de Jon, no funcionará con un código de terceros que no pueda o no pueda modificar.