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

时间:2014-05-11 21:29:51

标签: java unit-testing mocking mockito spy

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

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

    public void setup() {

    }
}

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


为什么需要对设置方法进行存根以使其不执行任何操作的原因:
我正在编写一个乐高机器人(lejos),我在机器人需要工作的设置中放了一些代码。但是,当我在TinyVM(安装在机器人上的VM)之外调用它时,java崩溃,因为VM没有正确初始化(因为测试在我的PC上运行)。对于单元测试,设置并不重要 我无法对类/方法设置调用进行存根,因为其中一些是公共静态最终变量。

3 个答案:

答案 0 :(得分:11)

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

更一般地说,如 Effective Java 第17项you should not call overridable methods from constructors中所述。例如,如果这样做,您可以在引用final字段但在设置final字段之前运行的子类中提供覆盖。它可能不会让你遇到麻烦,但它在Java中是一个坏习惯。

幸运的是,您可以重新构建代码,以便轻松完成此任务:

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

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

  /* ... */
}

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

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

  private MyClass(boolean runSetup) {
/* ... */

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

答案 1 :(得分: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());
    }

}

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


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

答案 2 :(得分:8)

  1. 使用PowerMock。

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

  3. 像这样:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({<Other classes>, Myclass.class})
    
    1. 在测试开始时取消该方法。
    2. 像这样:

      suppress(method(Myclass.class, "setup"));
      
      1. 在测试中根据需要自定义setup()方法的行为。
      2. 像这样:

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