使用Mockito模拟任何方法签名

7

你好,我有一段代码设计得不是很好,但我不是这段代码的所有者,所以无法更改。

public interface Car{ // This is a marker interface. }

public class BigCar implements Car{ 
  public boolean isVeryBig(){ 
  return true;}
} 

public class QuiteBigCar implements Car{ 
  public boolean isVeryBig(boolean withHatchBack){ 
  return true;}
}   

public Pickup implements Car{ 
  public boolean isVeryBig(boolean withHatchBack, boolean withRoofRack){ 
  return true;}
}
你只需要知道接口存在的意义在于让我知道 "BigCar","QuiteBigCar" 和 "Pickup" 都是 "Car"。虽然不是很聪明,但这就是我必须处理的问题。 现在我有一个方法,它接收一个汽车作为参数,并将返回一个模拟版本的汽车。 我希望该模拟版本能够确保每次调用 "isVeryBig()" 时,无论方法签名如何,都会返回 false。问题是,我没有一个通用的接口适用于所有的 "isVeryBig()" 方法;它们都有不同的签名。
public Car mockMyCar(Car car){

     Car mockedCar = mock(car);

     when(mockedCar.isVeryBig())  <-- This wont work since the method doesn't exist on the interface

    return mockedCar;
}

是的,我可以将Car转换为它们的子类,但对我来说这不是一个选项,因为有太多实现Car的类,对所有实现进行instanceOf检查会使代码非常混乱,而且我不能控制未来的Car新实现。

有什么想法吗?


5
这与Mockito无关。如果您通过“car”接口访问对象,则无法调用任何方法而不进行向下转换,更别提对其进行模拟了... - Giovanni Caporaletti
如果我是你,我会创建一个名为CarFixed的接口来声明方法,然后创建一个AbstractCarFixed类用于创建不同“实例”Car的静态工厂方法。这样,你至少可以模拟CarFixed - fge
@TrustNoOne 我同意这是一个基本的Java问题。然而,使用JMock有一种解决方法。我可以这样做:mockedCar.stubs().method("isVeryBig").will(returnValue(false)); 所以,是的,这是我现在使用Mockito面临的限制。 - pmartin8
@fge请记住,有许多汽车实现,并且我无法控制它们。我不是汽车实现类的所有者。由于我不能控制将来会存在什么样的汽车实现,因此无法自己创建工厂方法。 - pmartin8
1
mock(car) 也无法编译。你是在做类似于 mock(car.getClass()) 或者 spy(car) 这样的操作吗? - Michał Politowski
@MichałPolitowski,也许我给出的示例与我的代码不完全相同,但我的问题与方法签名有关...而不是模拟本身。 - pmartin8
1个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
5

以下几个选项按从最不严谨到最严谨的顺序:

  • Acknowledge that mocking a Car here makes as little sense as mocking a Serializable, and that you should actually pick an implementation to mock. You won't have the same trouble mocking a concrete BigCar or Pickup, and you can run through a few different implementations in a few test cases.

  • Refactor to a common properly-polymorphic interface as TrustNoOne described, rather than using ersatz polymorphism the way you have here. I know you may have your hands tied, but whoever stumbles across this question next might not.

  • You can do something similar to what you're describing—matching methods by name instead of signature—by supplying a default Answer when you create the mock:

    Car car = Mockito.mock(Car.class, new Answer<Object>() {
      @Override public Object answer(InvocationOnMock invocation) {
        if (invocation.getMethod().getName().equals("isVeryBig")) {
          return false;
        }
        // Delegate to the default answer.
        return Mockito.RETURNS_DEFAULTS.answer(invocation);
      }
    };
    

    Bear in mind, though, that your particular situation (of being able to downcast to one of a variety of implementations with different signatures) may require use of the extraInterfaces feature to let Java downcast as needed, which makes this entire thing tricky and fragile. You'd probably be better off using one of the other above solutions instead.


谢谢@Jeff Bowman,你的回答非常完整。虽然我意识到我的问题确实无法避免。我喜欢你最后的hacky版本,我一定会尝试的。 - pmartin8

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,