【问题标题】:How to mock new Date() in java using Mockito如何使用 Mockito 在 java 中模拟 new Date()
【发布时间】:2012-08-06 22:29:43
【问题描述】:

我有一个使用当前时间进行一些计算的函数。我想用 mockito 来模拟它。

我想测试的类的一个例子:

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

我想要类似的东西:

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

可以模拟吗?我不想更改“已测试”代码以便进行测试。

【问题讨论】:

  • 为什么不更改测试代码?更可测试的代码通常更松散耦合......你为什么不想要呢?
  • 模拟一个新日期不是一个好策略......如果你想要另一个日期:-)
  • 阅读我在stackoverflow.com/questions/11042200/… 上的回答,这是一个类似(但不相同)的问题。
  • @StephenC - 哈哈!在我意识到这是一个笑话之前,我不得不阅读你的评论大约 3 次。

标签: java unit-testing junit mocking mockito


【解决方案1】:

正确的做法是重组您的代码,使其更易于测试,如下所示。 重构你的代码以移除对 Date 的直接依赖将允许你为正常运行时和测试运行时注入不同的实现:

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);
    }
}

【讨论】:

  • 我同意。我一直都这样做。效果很好,对原始代码的更改很少,而且测试很容易。
  • 这是解决此问题的经典方法(您所称的DateTime 可能更具描述性地称为Clock 或类似名称)。但是,这确实意味着重组代码并增加一点复杂性,纯粹是为了进行测试,这有点代码味道。
  • 所以我做到了,我认为这是一个很好的方法,但问题是如何使用 Mockito 来做到这一点:D
  • 甚至更好:使用 JodaTime
  • 很遗憾答案没有回答问题。如果我的代码代码使用的库使用的库使用new Date() 并且我的测试停止工作,因为测试向量包含直到昨天有效的证书,我看不到如何从库中删除new Date() 的使用以快速修复我的测试。
【解决方案2】:

如果您有无法重构的遗留代码并且您不想影响System.currentTimeMillis(),请尝试使用PowermockPowerMockito

//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
  }
}

【讨论】:

  • 或者更好的是使用 JMockit(它可以做 Mockito 和 PowerMock 可以一起做的事情),所以你不需要两个不同的测试框架......
【解决方案3】:

可以通过使用PowerMock 来做到这一点,它增强了 Mockito 以模拟静态方法。然后你可以mock System.currentTimeMillis(),这是new Date() 最终获得时间的地方。

可以。我不会就你是否应该提出意见。

【讨论】:

  • 我有兴趣知道如何做这样的事情,这就是问题的目的。你有例子吗?
  • 这是来自Powermock documentation。应该与 PowerMockito 一样工作
  • 盲目地将每个“新”转换为包装器对象并通过注入的依赖项引入间接性只会使代码变得不必要的冗长和困难。如果您在类中创建 ArrayList 或 HashMap,您现在会创建 ArrayListFactory 或 HashMapFactory 并将其注入到您的类中吗?在您使用“新”的任何地方盲目地使用 PowerMock 也可以创建一个非常紧密耦合的系统。能够判断像 PowerMock 这样的工具在哪里适合是熟练的开发人员的一部分
【解决方案4】:

一种不直接回答问题但可能解决潜在问题(具有可重复测试)的方法是允许 Date 作为测试参数并将委托添加到默认日期。

像这样

public class ClassToTest {

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

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

在生产代码中,您使用getDoubleTime() 并针对getDoubleTime(Date date) 进行测试。

【讨论】:

    【解决方案5】:

    你也可以使用 jmockit 来模拟 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());
        }
    

    【讨论】:

      【解决方案6】:

      使用 PowerMockito 的 new Date()System.currentTimeMillis() 的工作示例。
      这是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);
          }
      
      }
      

      控制台输出

      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
      

      【讨论】:

        【解决方案7】:
        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;
                }
            });
        }
        

        【讨论】:

          猜你喜欢
          • 2019-03-13
          • 1970-01-01
          • 1970-01-01
          • 2021-04-30
          • 2011-03-13
          • 2020-03-30
          • 1970-01-01
          • 2022-06-15
          • 1970-01-01
          相关资源
          最近更新 更多