2012-08-09 25 views
26

Tengo una función que usa la hora actual para hacer algunos cálculos. Me gustaría burlarlo usando mockito.Cómo simular una nueva Fecha() en Java usando Mockito

Un ejemplo de la clase que me gustaría probar:

public class ClassToTest { 
    public long getDoubleTime(){ 
     return new Date().getTime()*2; 
    } 
} 

me gustaría algo así como:

@Test 
public void testDoubleTime(){ 
    mockDateSomeHow(Date.class).when(getTime()).return(30); 
    assertEquals(60,new ClassToTest().getDoubleTime()); 
} 

¿Es posible burlarse de eso? No me gustaría cambiar el código "probado" para poder probarlo.

+3

¿Por qué no cambiarías el código probado? El código que es más comprobable generalmente está más débilmente acoplado ... ¿por qué no querrías eso? – blank

+0

posible duplicado de [Ignorar Java System.currentTimeMillis] (http://stackoverflow.com/questions/2001671/override-java-system-currenttimemillis) –

+0

... y otra cosa: cambiar el código 'probado' es fácil - usted ' Tengo pruebas para decirte cuándo cometiste un error, cambiar el código no probado por otro lado ... necesitas Michael Feathers kung foo;) – blank

Respuesta

39

Lo correcto es reestructurar el código para que sea más comprobable, como se muestra a continuación. reestructuración de su código para eliminar la dependencia directa de la fecha le permitirá inyecta diferentes implementaciones de tiempo de ejecución y prueba normal en tiempo de ejecución:

interface DateTime { 
    Date getDate(); 
} 

class DateTimeImpl implements DateTime { 
    @Override 
    public Date getDate() { 
     return new Date(); 
    } 
} 

class MyClass { 

    private final DateTime dateTime; 
    // inject your Mock DateTime when testing other wise inject DateTimeImpl 

    public MyClass(final DateTime dateTime) { 
     this.dateTime = dateTime; 
    } 

    public long getDoubleTime(){ 
     return dateTime.getDate().getTime()*2; 
    } 
} 

public class MyClassTest { 
    private MyClass myClassTest; 

    @Before 
    public void setUp() { 
     final Date date = Mockito.mock(Date.class); 
     Mockito.when(date.getTime()).thenReturn(30L); 

     final DateTime dt = Mockito.mock(DateTime.class); 
     Mockito.when(dt.getDate()).thenReturn(date); 

     myClassTest = new MyClass(dt); 
    } 

    @Test 
    public void someTest() { 
     final long doubleTime = myClassTest.getDoubleTime(); 
     assertEquals(60, doubleTime); 
    } 
} 
+1

Estoy de acuerdo. Lo hago así todo el tiempo. Funciona muy bien, el cambio al código original es mínimo y probarlo es fácil. – stmax

+12

Esta es la manera clásica de resolver este problema (lo que usted llama 'DateTime' podría denominarse' Clock' o algo así más descriptivamente). Sin embargo, esto significa reestructurar su código y agregar un poco de complejidad puramente para permitir la prueba, que es un poco olor a código. –

+0

Así que lo hice, creo que es un buen enfoque, pero la pregunta era cómo hacerlo con Mockito: D –

6

Usted podría hacer esto mediante el uso de PowerMock, lo que aumenta Mockito para poder burlarse métodos estáticos A continuación, puede mock System.currentTimeMillis(), que es donde new Date() finalmente obtiene el tiempo de.

Usted podría. No voy a adelantar una opinión sobre si debe.

+1

Me interesa saber cómo hacer este tipo de cosas, ese es el objetivo de la pregunta. ¿Tienes ejemplos? –

+0

Esto es de la [documentación de Powermock] (http://code.google.com/p/powermock/wiki/MockSystem). Debería funcionar igual con PowerMockito – Brad

+1

Convertir ciegamente cada "nuevo" en un objeto envoltorio e introducir un direccionamiento indirecto a través de una dependencia inyectada simplemente hace que el código sea innecesariamente prolijo y difícil. Si estuviera creando un ArrayList o un HashMap dentro de su clase, ¿crearía ahora una ArrayListFactory o una HashMapFactory e inyectaría eso en su clase? Usar ciegamente PowerMock en todas partes donde use "nuevo" podría crear un sistema muy bien acoplado también. Saber dónde herramientas como PowerMock encajan es parte de ser un desarrollador experto – Aneesh

14

Si tiene el código heredado que no se puede refactorizar y no desea afectar System.currentTimeMillis(), intente esto utilizando Powermock y PowerMockito

//note the static import 
import static org.powermock.api.mockito.PowerMockito.whenNew; 

@PrepareForTest({ LegacyClassA.class, LegacyClassB.class }) 

@Before 
public void setUp() throws Exception { 

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
    sdf.setTimeZone(TimeZone.getTimeZone("PST")); 

    Date NOW = sdf.parse("2015-05-23 00:00:00"); 

    // everytime we call new Date() inside a method of any class 
    // declared in @PrepareForTest we will get the NOW instance 
    whenNew(Date.class).withNoArguments().thenReturn(NOW); 

} 

public class LegacyClassA { 
    public Date getSomeDate() { 
    return new Date(); //returns NOW 
    } 
} 
1

Un enfoque, que no responde directamente a la pregunta, pero podría resolver el subyacente problema (tener pruebas reproducibles), es permitir el Date como un parámetro para las pruebas y agregar un delegado a la fecha predeterminada.

Al igual que

public class ClassToTest { 

    public long getDoubleTime() { 
     return getDoubleTime(new Date()); 
    } 

    long getDoubleTime(Date date) { // package visibility for tests 
     return date.getTime() * 2; 
    } 
} 

En el código de producción, se utiliza getDoubleTime() y la prueba contra getDoubleTime(Date date).

Cuestiones relacionadas