Mockito间谍 - 在调用构造函数之前存根

Mat*_*o12 18 java unit-testing mocking mockito spy

我正在试图窥探一个Object,我想在构造函数调用之前存根一个由构造函数调用的方法.
我的班级看起来像这样:

public class MyClass {
    public MyClass() {
         setup();
    }

    public void setup() {

    }
}
Run Code Online (Sandbox Code Playgroud)

不得调用安装方法.那么,我如何监视这个方法(和存根设置,以便它什么都不做)?
它可以很好地模拟方法,但我想进行单元测试MyClass,所以我需要其他方法.


之所以需要存根设置方法以便它什么都不做:
我正在编写一个乐高机器人(lejos)并且我在设置中放置了一些机器人需要工作的代码.但是,当我在TinyVM(安装在机器人上的VM)之外调用它时,java崩溃,因为VM尚未正确初始化(因为测试在我的PC上运行).对于单元测试,设置并不重要.
我不能存根类/方法设置调用,因为它们中的一些是公共静态最终变量.

Jef*_*ica 12

要直接回答您的问题,您不能使用Mockito来存根从构造函数调用的方法.在你开始模拟之前,Mockito需要一个类的实例,而你没有给自己创建一个测试实例的方法.

更一般地说,如Effective Java第 17项中所述,您不应该从构造函数中调用可覆盖的方法.例如,如果这样做,您可以在引用final字段但在设置final字段之前运行的子类中提供覆盖.它可能不会让你遇到麻烦,但它在Java中是一个坏习惯.

幸运的是,您可以重构代码以轻松完成此操作:

public class MyClass {
  public MyClass() {
    this(true);
  }

  /** For testing. */
  MyClass(boolean runSetup) {
    if (runSetup) {
      setup();
    }
  }

  /* ... */
}
Run Code Online (Sandbox Code Playgroud)

为了使它更加明显,您可以将单参数MyClass构造函数设为私有,并提供public static工厂方法:

/* ... */
  public static MyClass createForTesting() {
    return new MyClass(false);
  }

  private MyClass(boolean runSetup) {
/* ... */
Run Code Online (Sandbox Code Playgroud)

虽然一些开发人员认为在主要用于测试的方法中编写任何代码是一种不好的做法,但请记住,您负责代码的设计,并且测试是您绝对知道需要适应的少数消费者之一.虽然在"生产"代码中避免显式测试设置仍然是一个好主意,但为了测试而创建额外的方法或重载通常会使您的代码整体更清晰,并且可以大大提高您的测试覆盖率和可读性.

  • @Michael:PowerMock存在,并且可以提供帮助,但使用它会违反更重要的规则:[不要嘲笑你不拥有的类型.](https://www.google.com/search?q =%22only + mock + types + you + own%22 + OR +%22never + OR + don%27t + mock +*+ you + don%27t + own%22)如果您正在测试的组件负责与a进行交互第三方服务,你测试一个虚假的实现你认为_第三方服务做什么,你实际测试的是什么?如果您的组件具有业务逻辑_和_第三方交互,为什么不是那两个单独的严格范围的组件呢? (2认同)

fra*_*orl 10

  1. 使用PowerMock.

  2. 导入库后,将其设置为使用不能调用的实例方法操作要模拟的类.

像这样:

@RunWith(PowerMockRunner.class)
@PrepareForTest({<Other classes>, Myclass.class})
Run Code Online (Sandbox Code Playgroud)
  1. 在测试开始时取消该方法.

像这样:

suppress(method(Myclass.class, "setup"));
Run Code Online (Sandbox Code Playgroud)
  1. 在测试中根据需要自定义setup()方法的行为.

像这样:

doAnswer(new Answer<Void>() {
      @Override
      public Void answer(InvocationOnMock invocation) throws Throwable {
           // code here
           return null;
      }
 }).when(Myclass.class, "setup");
Run Code Online (Sandbox Code Playgroud)

  • 如果我没记错的话,when(class) 仅适用于静态方法,而@MichaelOsofsky 试图模拟的方法是一个实例方法。除了覆盖具有非参数构造函数的类之外,似乎没有其他方法可以模拟它。使用参数构造函数,如果类没有一个,也不可能这样做。 (2认同)

Mat*_*o12 9

感谢您的建议,但有点过于复杂.
我最后通过扩展类并覆盖我的setup方法来模拟该方法.这样默认构造函数不会调用它的setup实现,而是调用覆盖的方法.
这是代码:

// src/author/MyClass.java

public class MyClass {
    public MyClass() {
        setup();
    }

    protected void setup() {
        throw new Exception("I hate unit testing !");
    }

    public boolean doesItWork() {
        return true;
    }
}

// test/author/MyClass.java

public class MyClassTest {
    private class MockedMyClass extends MyClass {
        @Override
        protected void setup() {

        }
    }

    private MyClass instance;

    @Before
    public void setUp() { // Not to be confusing with `MyClass#setup()`!
        instance = new MockedMyClass();
    }

    @Test
    public void test_doesItWork() {
        assertTrue(instance.doesItWork());
    }

}
Run Code Online (Sandbox Code Playgroud)

如果您不希望MyTest的设置方法被其他子类调用或覆盖,除了您的测试(因为其他开发人员可能通过使用安装方法非常糟糕),只需将可见性更改为默认值,只有您的类才能够呼叫设置.


如果有一种更简单的方法,请回答这个问题,因为我不是100%满意我的解决方案.