55

I have a function that uses the current time to make some calculations. I'd like to mock it using mockito.

An example of the class I'd like to test:

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

I'd like something like:

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

Is it possible to mock that? I wouldn't like to change the "tested" code in order to be tested.

Marian Paździoch
  • 8,813
  • 10
  • 58
  • 103
Jordi P.S.
  • 3,838
  • 7
  • 36
  • 59
  • 3
    Why wouldn't you change the tested code? Code that is more testable is generally more loosely coupled ... why wouldn't you want that? – blank Aug 09 '12 at 16:27
  • possible duplicate of [Override Java System.currentTimeMillis](http://stackoverflow.com/questions/2001671/override-java-system-currenttimemillis) – Jon Skeet Aug 09 '12 at 16:30
  • ... and another thing - changing 'tested' code is easy - you've got tests to tell you when you made a mistake - changing un-tested code on the other hand ... you need Michael Feathers kung foo ;) – blank Aug 09 '12 at 16:46
  • 11
    Mocking a new date is not a good strategy ... if you want another date :-) – Stephen C Aug 09 '12 at 16:51
  • 1
    Read my answer on http://stackoverflow.com/questions/11042200/can-i-use-mockito-to-match-an-object-with-an-auto-updated-timestamp, which is a similar (but not identical) problem. – Dawood ibn Kareem Aug 09 '12 at 23:03
  • 9
    @StephenC - Haha! I had to read your comment about 3 times before I realised it was a joke. – Dawood ibn Kareem Aug 09 '12 at 23:05

7 Answers7

70

The right thing to do is to restructure your code to make it more testable as shown below. Restructuring your code to remove the direct dependency on Date will allow you to inject different implementations for normal runtime and test 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);
    }
}
munyengm
  • 15,029
  • 4
  • 24
  • 34
  • 1
    I concur. I do it like that all the time. Works great, the change to the original code is minimal and testing it is easy. – stmax Aug 09 '12 at 16:37
  • 29
    This is the classic way to solve this problem (what you call `DateTime` might more descriptively be called `Clock` or something like that). However, this does mean restructuring your code and adding a little bit of complexity purely to allow testing, which is a bit of a code smell. – Tom Anderson Aug 09 '12 at 20:09
  • 4
    So I did, I think it's a good aproach, but the question was how to do it with Mockito :D – Jordi P.S. Aug 09 '12 at 21:07
  • Just small question why you use Date class instead of System.currentTimeMillis()? – Eugen Martynov Aug 10 '12 at 05:36
  • The code there is was purely a sample, not prod code, but actually I'm using new Date(). Why? Cause I'm working with dates, not with Longs. I save a type conversion. My db stores dates (appengine datastore). – Jordi P.S. Aug 10 '12 at 08:08
  • 1
    Or even better : use JodaTime – bric3 Aug 10 '12 at 08:29
  • 3
    Sad the answer doesn't answer the question. If my code code uses a library which uses a library which uses `new Date()` and my test stopped working because the test vector contained a certificate valid until yesterday, I don't see how to remove the use of `new Date()` from the library to quickly fix my test. – Giszmo Jun 11 '19 at 18:55
  • @Giszmo It answers the original question as posed by the OP. Sounds like you have a different question/requirement. Best post this as a new question. – munyengm Aug 09 '19 at 10:33
  • When using new Date(), the code is inherently impure, and will only give the same result when running at a specific point in time, so I feel this solution makes the method "more" pure - and does not add too much complexity – Mz A May 22 '21 at 10:32
  • How would you test `DateTimeImpl`? – tim-phillips Jun 06 '23 at 22:49
29

If you have legacy code that you cannot refactor and you do not want to affect System.currentTimeMillis(), try this using Powermock and 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
  }
}
Ashish Kumar
  • 916
  • 2
  • 15
  • 32
Daniel Mora
  • 2,589
  • 1
  • 25
  • 21
  • Or better yet use JMockit (which can do what Mockito and PowerMock can do together) so you don't need two different testing framworks... – Miyagi Dec 06 '18 at 09:15
  • can someone help me resolve this? https://stackoverflow.com/questions/74775990/java-powermock-cannot-resolve-symbol-preparefortest – mattsmith5 Dec 12 '22 at 18:55
6

You could do this by using PowerMock, which augments Mockito to be able to mock static methods. You could then mock System.currentTimeMillis(), which is where new Date() ultimately gets the time from.

You could. I'm not going to advance an opinion on whether you should.

Tom Anderson
  • 46,189
  • 17
  • 92
  • 133
  • 1
    I'm interested in knowing how to do such a things, that's the aim of the question. Do you have examples ? – Jordi P.S. Aug 09 '12 at 21:07
  • This is from the [Powermock documentation](http://code.google.com/p/powermock/wiki/MockSystem). Should work the same with PowerMockito – Brad Aug 09 '12 at 22:13
  • 5
    Blindly turning every "new" into a wrapper object and introducing an indirection through an injected dependency just makes the code un-necessarily verbose and hard. If you were creating an ArrayList or a HashMap inside your class would you now create an ArrayListFactory or a HashMapFactory and inject that into your class? Blindly using PowerMock everywhere you use "new" could create a very tightly coupled system as well. Being able to tell where tools like PowerMock are a good fit is part of being a skilled developer – Aneesh Sep 04 '14 at 14:58
  • 1
    Apparently you can not mock the System class anymore (I am using Mockito 3.12). It says: "It is not possible to mock static methods of java.lang.System to avoid interfering with class loading what leads to infinite loops". – Daniel Beer Jun 21 '23 at 07:38
1

One approach, that does not directly answer the question but might solve the underlying problem (having reproducible tests), is allow the Date as an parameter for tests and add a delegate to the default date.

Like so

public class ClassToTest {

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

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

In production code, you use getDoubleTime() and test against getDoubleTime(Date date).

DerMike
  • 15,594
  • 13
  • 50
  • 63
1

Working example with new Date() and System.currentTimeMillis() using PowerMockito.
Here is an example for Instance.

@RunWith(PowerMockRunner.class)
@PrepareForTest(LegacyClass.class) // prepares byte-code of the LegacyClass
public class SystemTimeTest {
    
    private final Date fakeNow = Date.from(Instant.parse("2010-12-03T10:15:30.00Z"));

    @Before
    public void init() throws Exception {
        // mock new Date()
        PowerMockito.whenNew(Date.class).withNoArguments().thenReturn(fakeNow);
        System.out.println("Fake now: " + fakeNow);

        // mock System.currentTimeMillis()
        PowerMockito.mockStatic(System.class);
        PowerMockito.when(System.currentTimeMillis()).thenReturn(fakeNow.getTime());
        System.out.println("Fake currentTimeMillis: " + System.currentTimeMillis());
    }

    @Test
    public void legacyClass() {
        LegacyClass legacyClass = new LegacyClass();
        legacyClass.methodWithNewDate();
        legacyClass.methodWithCurrentTimeMillis();
    }

}

class LegacyClass {

    public void methodWithNewDate() {
        Date now = new Date();
        System.out.println("LegacyClass new Date() is " + now);
    }

    public void methodWithCurrentTimeMillis() {
        long now = System.currentTimeMillis();
        System.out.println("LegacyClass System.currentTimeMillis() is " + now);
    }

}

Console output

Fake now: Fri Dec 03 16:15:30 NOVT 2010
Fake currentTimeMillis: 1291371330000
LegacyClass new Date() is Fri Dec 03 16:15:30 NOVT 2010
LegacyClass System.currentTimeMillis() is 1291371330000
Sergey Nemchinov
  • 1,348
  • 15
  • 21
0

you can also use jmockit to mock new Date():

    @Test
    public void mockTime() {
        new MockUp<System>() {
            @Mock
            public long currentTimeMillis() {
                // Now is always 11/11/2021
                Date fake = new Date(121, Calendar.DECEMBER, 11);
                return fake.getTime();
            }
        };
        Assert.assertEquals("mock time failed", new Date(121, Calendar.DECEMBER, 11), new Date());
    }
peter zhang
  • 1,247
  • 1
  • 10
  • 19
-1
Date now = new Date();    
now.set(2018, Calendar.FEBRUARY, 15, 1, 0); // set date to 2018-02-15
//set current time to 2018-02-15
mockCurrentTime(now.getTimeInMillis());

private void mockCurrentTime(long currTimeUTC) throws Exception {
    // mock new dates with current time
    PowerMockito.mockStatic(Date.class);
    PowerMockito.whenNew(Date.class).withNoArguments().thenAnswer(new Answer<Date>() {

        @Override
        public Date answer(InvocationOnMock invocation) throws Throwable {
            return new Date(currTimeUTC);
        }
    });

    //do not mock creation of specific dates
    PowerMockito.whenNew(Date.class).withArguments(anyLong()).thenAnswer(new Answer<Date>() {

        @Override
        public Date answer(InvocationOnMock invocation) throws Throwable {
            return new Date((long) invocation.getArguments()[0]);
        }
    });

    // mock new calendars created with time zone
    PowerMockito.mockStatic(Calendar.class);
    Mockito.when(Calendar.getInstance(any(TimeZone.class))).thenAnswer(new Answer<Calendar>() {
        @Override
        public Calendar answer(InvocationOnMock invocation) throws Throwable {
            TimeZone tz = invocation.getArgumentAt(0, TimeZone.class);
            Calendar cal = Calendar.getInstance(tz);
            cal.setTimeInMillis(currTimeUTC);
            return cal;
        }
    });
}
Avraham Shalev
  • 110
  • 1
  • 3