2012-08-09 8 views
26

Ho una funzione che utilizza l'ora corrente per fare alcuni calcoli. Mi piacerebbe deriderlo usando il mockito.Come beffe new Date() in Java utilizzando Mockito

Un esempio di classe mi piacerebbe provare:

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

mi piacerebbe qualcosa di simile:

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

E 'possibile prendere in giro così? Non vorrei cambiare il codice "testato" per essere testato.

+3

Perché non dovresti cambiare il codice testato? Il codice che è più testabile è generalmente più strettamente accoppiato ... perché non vorresti quello? – blank

+0

possibile duplicato di [override Java System.currentTimeMillis] (http://stackoverflow.com/questions/2001671/override-java-system-currenttimemillis) –

+0

... e un'altra cosa - codice 'testato' che cambia è facile - si' ve test avuto modo di dirvi quando hai fatto un errore - la modifica del codice non-testata d'altra parte ... hai bisogno di Michael Feathers kung foo;) – blank

risposta

39

La cosa giusta da fare è di ristrutturare il codice per renderlo più verificabili come illustrato di seguito. Ristrutturazione il codice per rimuovere la dipendenza diretta sulla data vi permetterà di iniettare diverse implementazioni per il normale runtime e test di runtime:

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

Concordo. Lo faccio sempre. Funziona alla grande, la modifica al codice originale è minima e il test è semplice. – stmax

+12

Questo è il modo classico per risolvere questo problema (quello che tu chiami "DateTime" potrebbe essere più descrittivamente chiamato "Orologio" o qualcosa del genere). Tuttavia, ciò significa ristrutturare il codice e aggiungere un po 'di complessità puramente per consentire il test, che è un po' un odore di codice. –

+0

Così ho fatto, penso che sia un buon approccio, ma la domanda era come farlo con Mockito: D –

6

È potrebbe farlo utilizzando PowerMock, che aumenta Mockito essere in grado di prendere in giro metodi statici. Si potrebbe quindi mock System.currentTimeMillis(), che è il punto in cui il tempo è il new Date().

È potrebbe. Non ho intenzione di avanzare un'opinione sul fatto che sia .

+1

Sono interessato a sapere come fare una cosa del genere, questo è lo scopo della domanda. Hai esempi? –

+0

Si tratta della [documentazione di Powermock] (http://code.google.com/p/powermock/wiki/MockSystem). Dovrebbe funzionare allo stesso modo con PowerMockito – Brad

+1

Trasformando in modo blando ogni "nuovo" in un oggetto wrapper e introducendo un'indirizzamento indiretto attraverso una dipendenza iniettata, il codice diventa necessariamente prolisso e prolisso. Se stavi creando una ArrayList o una HashMap all'interno della tua classe, ora creerai una ArrayListFactory o una HashMapFactory e la inserirai nella tua classe? Usare ciecamente PowerMock ovunque utilizzi "nuovo" potrebbe anche creare un sistema strettamente accoppiato. Essere in grado di dire dove strumenti come PowerMock sono una buona misura fa parte di essere uno sviluppatore esperto – Aneesh

14

Se si dispone di codice legacy che non si può refactoring e non si vuole incidere System.currentTimeMillis(), provate questo utilizzando Powermock e 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 approccio, che non risponde direttamente alla domanda, ma potrebbe risolvere il sottostante problema (con test riproducibili), è consentire il Date come parametro per i test e aggiungere un delegato alla data predefinita.

Come così

public class ClassToTest { 

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

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

Nel codice di produzione, si utilizza getDoubleTime() e il test contro getDoubleTime(Date date).

Problemi correlati