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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 14:08:13  来源:igfitidea点击:

Mockito: How to mock an interface of JodaTime

javajunitjodatimemockito

提问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 printJobNamedepend 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 XNCRMNCFis the name for before 4PM. Do I need to mock printProcessoras 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 JodaTimebecause 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 JodaTimesomewhere 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:JodaTimeis 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 printjobNameto 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 JodaTimeis 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 PrintProcessoryour 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 PrintProcessoryour mock:

你永远不会给PrintProcessor你的模拟。对对象进行模拟与将模拟提供给对象不同。因此,当您在 上调用方法时PrintProcessor,它正在对 的真实实例进行操作JodaTime。有几种方法可以给PrintProcessor你的模拟:

  1. Use PowerMockito(make sure you use PowerMock-mockito jar and not PowerMock-easymock jar) and mock the JodaTime constructor to return your mocked Clockobject whenNew(JodaTime.class).withNoArguments().thenReturn(mockJodaTime);This will insert your mock wherever a no-arg constructor for JodaTimeis used. Note: This will require you to use a mock of the JodaTimeclass.
  2. Add a setter method for the Clock jodaTimefield (which, if only defined in the constructor should probably be final).
  3. Use a Factory for your Clockclass and simply return the mock during tests (you can use PowerMockito to mock static methods).
  4. Create a constructor with a Clockparameter and pass in your mock.
  1. 使用PowerMockito(确保您使用 PowerMock-mockito jar 而不是 PowerMock-easymock jar)并模拟 JodaTime 构造函数以返回您的模拟Clock对象whenNew(JodaTime.class).withNoArguments().thenReturn(mockJodaTime);这将在使用无参数构造函数的任何地方插入您的模拟JodaTime。注意:这将要求您使用JodaTime类的模拟。
  2. 为该Clock jodaTime字段添加一个 setter 方法(如果只在构造函数中定义,则可能应该是final)。
  3. 为您的Clock类使用工厂并在测试期间简单地返回模拟(您可以使用 PowerMockito 来模拟静态方法)。
  4. 创建一个带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 调用设置期望值。