Mockito:如何模拟JodaTime的界面

我使用JodaTime#DateTime ,我需要模拟它的行为。 由于无法直接模拟JodaTime#DateTime ,因此我创建了它的接口

Clock.java

 public interface Clock { DateTime getCurrentDateTimeEST(); DateTime getFourPM_EST(); DateTime getSevenPM_EST(); } 

JodaTime.java

 public class JodaTime implements Clock { @Override public DateTime getCurrentDateTimeEST() { return new DateTime(DateTimeZone.forID("EST")); } @Override public DateTime getFourPM_EST() { DateTime current = getCurrentDateTimeEST(); return new DateTime(current.getYear(), current.getMonthOfYear(), current.getDayOfMonth(), 16, 0, 0, 0, DateTimeZone.forID("EST")); } @Override public DateTime getSevenPM_EST() { DateTime current = getCurrentDateTimeEST(); return new DateTime(current.getYear(), current.getMonthOfYear(), current.getDayOfMonth(), 19, 0, 0, 0, DateTimeZone.forID("EST")); } } 

这是我想测试的方法

 public class PrintProcessor{ Clock jodaTime; public PrintProcessor(){ jodaTime = new JodaTime(); } ... public String getPrintJobName(Shipper shipper){ String printJobName = null; //Get current EST time if(jodaTime.getCurrentDateTimeEST().isBefore(jodaTime.getFourPM_EST()) || jodaTime.getCurrentDateTimeEST().isAfter(jodaTime.getSevenPM_EST())){ //Before 4PM EST and after 7PM EST switch(shipper){ case X: ... }else if(jodaTime.getCurrentDateTimeEST().isBefore(jodaTime.getSevenPM_EST())){ //Between 4PM-7PM EST switch(shipper){ case X: ... } return printJobName; } } 

如您所见, printJobName取决于当天的当前时间相对于时间间隔[4 PM-7PM] EST和出货单名称。 由于Shipper将通过参数传递,我们可以对它进行unit testing没问题。 但我需要嘲笑时间。 所以这就是我的尝试

 @Test public void testGetPrintJobNameBeforeFourPM(){ DateTime current = new DateTime(DateTimeZone.forID("EST")); Clock clock = mock(Clock.class); //Always return 6pm when I try to ask for the current time when(clock.getCurrentDateTimeEST()).thenReturn(new DateTime(current.getYear(), current.getMonthOfYear(), current.getDayOfMonth(), 18, 0, 0, 0, DateTimeZone.forID("EST"))); //Test for Fedex String printJobName = printProcessor.getPrintJobName(Shipper.X); assertEquals("XNCRMNCF", printJobName); } 

由于我在下午6点通过,测试应该失败,但是XNCRMNCF是下午4点之前的名称。 我是否也需要模拟printProcessor 。 如果我有什么不对。 我应该怎么解决? 我正在努力学习编写高级java代码,请非常批评我的代码。 我真的很想学

这是一个经典的测试案例,显示了设计中的潜在缺陷。 您无法模拟JodaTime因为您在被测试的类中对这些类具有硬连线依赖性。

看看SOLID原理 ,了解为什么这可能是一个问题(特别是在这种情况下依赖倒置原则 )。 如果你把JodaTime作为依赖项注入某个地方,那么在你的unit testing中,你可以根据需要用模拟,存根或间谍替换它的真实实例。

然而: JodaTime长, JodaTime都不太可能在生产环境中注入任何其他东西。 相反,在这种情况下,您可能会更好地使用组合方法设计模式 。 在这里,您将提取用于生成printjobName任何计算/算法到另一个方法(我在这里看不到你是如何做到的,因为你的代码片段从不为该变量赋值)。 然后,您可以间谍(部分模拟)您的测试类,仅模拟该方法并返回固定值,而不管JodaTime提供的实际日期时间,例如:

 public class PrintProcessor { ... public String getPrintJobName(Shipper shipper) { String printJobName = null; String timeHash = this.getTimeHash(); if (this.isBeforeFourPM()) { switch(shipper) { printJobName = // Do something with timeHash to generate name } } else { ... } return printJobName; } public boolean isBeforeFourPM() { return (jodaTime.getCurrentDateTimeEST().isBefore(jodaTime.getFourPM_EST()) || jodaTime.getCurrentDateTimeEST().isAfter(jodaTime.getSevenPM_EST())); } public String getTimeHash() { ... // Do something to hash the time value in to a String } } 

现在你可以写下你的测试了:

 @Test public void testGetPrintJobNameBeforeFourPM() { PrintProcessor concretePrintProcessor = new PrintProcessor(); PrintProcessor printProcessor = spy(concretePrintProcessor); doReturn(true).when(printProcessor).isBeforeFourPM(); String printJobName = printProcessor.getPrintJobName(Shipper.X); assertEquals("XNCRMNCF", printJobName); } 

你永远不会给PrintProcessor你的模拟。 模拟对象与将模拟赋予对象不同。 因此,当您在PrintProcessor上调用方法时,它正在PrintProcessor的实际实例上JodaTime 。 有几种方法可以为PrintProcessor模拟:

  1. 使用PowerMockito (确保你使用PowerMock-mockito jar而不是PowerMock-easymock jar)并模拟JodaTime构造函数以返回你的模拟Clock对象whenNew(JodaTime.class).withNoArguments().thenReturn(mockJodaTime); 这将在使用JodaTime的no-arg构造函数的任何地方插入模拟。 注意:这将要求您使用JodaTime类的模拟。
  2. Clock jodaTime字段添加一个setter方法(如果只在构造函数中定义,那么它应该是final )。
  3. 使用Factory作为Clock类,只需在测试期间返回模拟(您可以使用PowerMockito来模拟静态方法)。
  4. 使用Clock参数创建一个构造函数并传入mock。

我认为你肯定是在正确的道路上。 创建用于模拟的Clock接口绝对是个好主意。

我在代码中没有看到一件事:将模拟时钟注入printProcessor。 创建模拟后,我认为你需要的东西是:

 printProcessor.setClock(clock) 

(这是在你调用getPrintJobName之前。这个setter应该在你的PrintProcessor类中设置jodaTime属性)

我习惯了EasyMock,所以我可能错了,但我很确定你还需要设置getFourPM_EST和getSevenPM_EST调用的期望值。