How to mock new Date() in java using Mockito
Asked Answered
H

8

58

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.

Hunan answered 9/8, 2012 at 16:24 Comment(6)
Why wouldn't you change the tested code? Code that is more testable is generally more loosely coupled ... why wouldn't you want that?Palais
possible duplicate of Override Java System.currentTimeMillisHyphenate
... 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 ;)Palais
Mocking a new date is not a good strategy ... if you want another date :-)Aristate
Read my answer on #11042700, which is a similar (but not identical) problem.Kapellmeister
@StephenC - Haha! I had to read your comment about 3 times before I realised it was a joke.Kapellmeister
P
72

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);
    }
}
Polynices answered 9/8, 2012 at 16:34 Comment(10)
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.Consternate
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.Footless
So I did, I think it's a good aproach, but the question was how to do it with Mockito :DHunan
Just small question why you use Date class instead of System.currentTimeMillis()?Andreandrea
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).Hunan
Or even better : use JodaTimeSwetlana
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.Insuppressible
@Insuppressible 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.Polynices
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 complexityNeuroticism
How would you test DateTimeImpl?Sherronsherry
T
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
  }
}
Typhogenic answered 23/5, 2015 at 17:6 Comment(2)
Or better yet use JMockit (which can do what Mockito and PowerMock can do together) so you don't need two different testing framworks...Immunogenetics
can someone help me resolve this? #74776490Adiana
F
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.

Footless answered 9/8, 2012 at 20:13 Comment(4)
I'm interested in knowing how to do such a things, that's the aim of the question. Do you have examples ?Hunan
This is from the Powermock documentation. Should work the same with PowerMockitoTitty
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 developerElectroform
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".Cresa
H
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).

Hugues answered 5/7, 2016 at 16:49 Comment(0)
A
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
Advanced answered 2/4, 2021 at 14:20 Comment(0)
G
1

This question has gotten a lot of traction, and Mockito has improved a lot since 2012. PowerMockito has been unmaintained since 2020, and most of PowerMockito's functionality has been migrated into Mockito since v4+ in 2024. This includes mocking constructor functions using Mockito v4+.

Note that for Mockito v4+, you will need to manually import the mockito-inline dependency in your pom file (Mockito v5+ uses the inline maker by default, so no need to change your pom file):

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-inline</artifactId>
    <scope>test</scope>
</dependency>

For mocking the Date constructor, you don't need to restructure your code to inject Date anymore as @munyengm's answer mentions. Mockito has a dedicated mockConstruction method now. Here's an MVE: MyService.java

class MyService {
    public Date generateDate() {
        return new Date();
    }
}

MyServiceTest.java

@ExtendWith(MockitoExtension.class)
class MyServiceTest {
    MyService myService = new MyService();

    @Test
    void testGenerateDate() {
        try(var __ = mockConstruction(Date.class, (mock, ctx) -> when(mock.getTime()).thenReturn(1000L))) {
            var nextDate = myService.generateDate();
            assertEquals(new Date(1000L).getTime(), nextDate.getTime());
        }
    }
}

The mockConstruction method tells Mockito to make a mock Date object whenever the Date constructor gets called. Then the only thing we need to do is define stubs for whatever behavior we want the class to simulate when its methods are called.

In the above example, I am telling the Date mock to return 1000L for the time whenever the mock object's getTime() method gets called.

There are other variations of the mockConstruction method that are worth looking into for more complex scenarios, such as returning a different response each time getTime() is called. More info is available in this Baeldung article.

Greeting answered 3/4 at 14:17 Comment(0)
T
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());
    }
Thereunder answered 28/3, 2021 at 3:11 Comment(0)
B
-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;
        }
    });
}
Baud answered 10/4, 2018 at 10:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.