How to mock new Date() in java using Mockito

前端 未结 5 1107
失恋的感觉
失恋的感觉 2020-11-29 05:21

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:



        
相关标签:
5条回答
  • 2020-11-29 05:31

    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.

    0 讨论(0)
  • 2020-11-29 05:32
    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;
            }
        });
    }
    
    0 讨论(0)
  • 2020-11-29 05:33

    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
      }
    }
    
    0 讨论(0)
  • 2020-11-29 05:34

    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).

    0 讨论(0)
  • 2020-11-29 05:41

    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);
        }
    }
    
    0 讨论(0)
提交回复
热议问题