java Mockito:如何模拟 JodaTime 的接口
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6049777/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Mockito: How to mock an interface of JodaTime
提问by Thang Pham
I use JodaTime#DateTime
, and I need to mock its behavior. Since it is not possible to directly mock JodaTime#DateTime
, I create an interface of it
我使用JodaTime#DateTime
,我需要模拟它的行为。由于无法直接模拟JodaTime#DateTime
,我创建了它的接口
Clock.java
时钟.java
public interface Clock {
DateTime getCurrentDateTimeEST();
DateTime getFourPM_EST();
DateTime getSevenPM_EST();
}
JodaTime.java
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"));
}
}
Here is the method that I want to test
这是我要测试的方法
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;
}
}
As you can see the printJobName
depend on the current time of the day relative to the time interval [4PM-7PM] EST and the Shipper name. Since Shipper will be pass via parameter, we can unit test it no problem. But I need to mock the time. So here is what I try
如您所见,这printJobName
取决于相对于美国东部标准时间 [4PM-7PM] 时间间隔和托运人名称的当前时间。由于 Shipper 将通过参数传递,我们可以对其进行单元测试,没有问题。但我需要嘲笑时间。所以这就是我的尝试
@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);
}
The test should fail since I pass in 6PM, but XNCRMNCF
is the name for before 4PM. Do I need to mock printProcessor
as well. If what I have is wrong. How should I fix it? I am trying to learn writing high level java code, please be very criticized about my code. I really want to learn
测试应该会失败,因为我在下午 6 点通过,但它XNCRMNCF
是下午 4点之前的名称。我也需要嘲笑吗printProcessor
。如果我所拥有的是错误的。我该如何解决?我正在尝试学习编写高级 Java 代码,请批评我的代码。我真的很想学习
采纳答案by Alan Escreet
This is a classic case of testing showing up a potential flaw in design. You cannot mock JodaTime
because you have a hard-wired dependency to these classes in your class-under-test.
这是一个典型的测试案例,显示了设计中的潜在缺陷。您不能模拟,JodaTime
因为您在被测类中对这些类具有硬连线依赖性。
Have a look at the SOLID principlesto understand why this could be a problem (especially in this case the Dependency Inversion Principle). If you injected JodaTime
somewhere as a dependency, then in your unit test you would be able to replace a real instace of it with a mock, stub or spyas appropriate.
查看SOLID 原则以了解为什么这可能是一个问题(特别是在这种情况下是依赖倒置原则)。如果您将JodaTime
某个地方作为依赖项注入,那么在您的单元测试中,您将能够适当地用模拟、存根或间谍替换它的真实实例。
However:JodaTime
is something that is highly unlikely to be injected with anything else in the production environment, no matter how long it is live for. Instead, in this case you would probably be better served with the Composed Method Design Pattern. Here, you would extract whatever calculation/algorithm you use to generate the printjobName
to another method (I can't see how you do it here because your code snippet never assigns a value to that variable). Then you can spy (partial mock) your class under test to only mock that method and return a fixed value, regardless of the real date time that JodaTime
is delivering, for instance:
然而: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
}
}
Now you can write in your test:
现在你可以在你的测试中写:
@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);
}
回答by pickypg
You are never giving the PrintProcessor
your mock. Making a mock of the object is not the same as giving your mock to an object. So, when you call methods on PrintProcessor
, it is operating on a real instance of JodaTime
. There are couple of ways to give the PrintProcessor
your mock:
你永远不会给PrintProcessor
你的模拟。对对象进行模拟与将模拟提供给对象不同。因此,当您在 上调用方法时PrintProcessor
,它正在对 的真实实例进行操作JodaTime
。有几种方法可以给PrintProcessor
你的模拟:
- Use PowerMockito(make sure you use PowerMock-mockito jar and not PowerMock-easymock jar) and mock the JodaTime constructor to return your mocked
Clock
objectwhenNew(JodaTime.class).withNoArguments().thenReturn(mockJodaTime);
This will insert your mock wherever a no-arg constructor forJodaTime
is used. Note: This will require you to use a mock of theJodaTime
class. - Add a setter method for the
Clock jodaTime
field (which, if only defined in the constructor should probably befinal
). - Use a Factory for your
Clock
class and simply return the mock during tests (you can use PowerMockito to mock static methods). - Create a constructor with a
Clock
parameter and pass in your mock.
- 使用PowerMockito(确保您使用 PowerMock-mockito jar 而不是 PowerMock-easymock jar)并模拟 JodaTime 构造函数以返回您的模拟
Clock
对象whenNew(JodaTime.class).withNoArguments().thenReturn(mockJodaTime);
这将在使用无参数构造函数的任何地方插入您的模拟JodaTime
。注意:这将要求您使用JodaTime
类的模拟。 - 为该
Clock jodaTime
字段添加一个 setter 方法(如果只在构造函数中定义,则可能应该是final
)。 - 为您的
Clock
类使用工厂并在测试期间简单地返回模拟(您可以使用 PowerMockito 来模拟静态方法)。 - 创建一个带
Clock
参数的构造函数并传入你的模拟。
回答by Peter
I think you're definitely on the right path. Creating the Clock interface for mocking is definitely a good idea.
我认为你绝对是在正确的道路上。创建用于模拟的时钟接口绝对是一个好主意。
One thing I don't see in your code: injecting the mocked clock into the printProcessor. After creating the mock, I think you need something along the lines of:
我在您的代码中没有看到的一件事是:将模拟时钟注入到 printProcessor 中。创建模拟后,我认为您需要以下内容:
printProcessor.setClock(clock)
(this goes before you call getPrintJobName. This setter should set the jodaTime property in your PrintProcessor class)
(这在你调用 getPrintJobName 之前进行。这个 setter 应该在你的 PrintProcessor 类中设置 jodaTime 属性)
I'm used to EasyMock, so I might be wrong, but I'm pretty sure you will also need to set expectations for the getFourPM_EST and getSevenPM_EST calls.
我习惯于 EasyMock,所以我可能错了,但我很确定您还需要为 getFourPM_EST 和 getSevenPM_EST 调用设置期望值。