【问题标题】:Mockito Spy - stub before calling the constructorMockito Spy - 调用构造函数之前的存根
【发布时间】:2014-05-11 21:29:51
【问题描述】:

我正在尝试监视一个对象,并且我想在构造函数调用它之前存根由构造函数调用的方法。
我的班级是这样的:

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

    public void setup() {

    }
}

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


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

【问题讨论】:

  • 是从MyClass'构造函数调用的setup()?您的问题似乎暗示了这一点,但代码 sn-p 没有。
  • @Mureinik 谢谢。我忘记添加了。
  • 这不能直接完成,因为Mockito.spy 是在预先存在的实例上调用的。
  • 您是否考虑过将“设置”代码委托给不同的对象? (例如,由接口IMyClassInitializer 表示)如果你这样做了,你可以创建一个额外的构造函数来获取这个接口的一个实例,你的测试可以传递一个空对象实现。 (并且您的默认构造函数可以传递“默认”实现。)

标签: java unit-testing mocking mockito spy


【解决方案1】:

要直接回答您的问题,您不能使用 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) {
/* ... */

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

【讨论】:

  • 嗨@JeffBowman,感谢您直接回答问题:使用 Mockito 是不可能的。问题是你不能总是改变你正在测试的代码。 fragorl 的 PowerMock 答案很棒,因为它允许您测试依赖于您无法控制的 3rd 方库的代码。
  • @Michael:PowerMock 存在,并且可能会有所帮助,但在那里使用它会违反更重要的规则:Don't mock types you don't own. 如果您正在测试的组件负责与第三方交互服务,并且您针对您认为第三方服务所做的虚假实现进行测试,您实际测试的是什么?如果你的组件有业务逻辑第三方交互,那为什么不是这两个独立的紧密范围的组件呢?
  • 谢谢,我刚刚阅读了您的一些帖子以更好地理解您的观点。您是否有一些我可以阅读的示例,以查看设计分离业务逻辑和第三方交互的单独范围狭窄的组件的示例?
【解决方案2】:
  1. 使用 PowerMock。

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

像这样:

@RunWith(PowerMockRunner.class)
@PrepareForTest({<Other classes>, Myclass.class})
  1. 在测试开始时禁止该方法。

像这样:

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

像这样:

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

【讨论】:

  • 哇@fragorl,PowerMock 似乎很适合这个!但是 PowerMock 是否与 Mockito 相提并论?
  • 如果我没记错的话,when(class) 仅适用于静态方法,而@MichaelOsofsky 试图模拟的方法是实例方法。除了覆盖具有非参数构造函数的类之外,似乎没有其他方法可以模拟它。带参数的构造函数,如果类没有一个,也不可能做到。
  • 这是最好的答案
【解决方案3】:

感谢您的建议,但它有点太复杂了。
我最终通过扩展类并覆盖我的设置方法来模拟该方法。这样默认构造函数就不会调用它的 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 的 setup 方法被除您的测试之外的其他子类调用或覆盖(因为其他开发人员可能会使用 setup 方法将事情搞砸),只需将可见性更改为默认值,并且只更改您的类将能够调用设置。


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

【讨论】:

  • 我最喜欢你的回答(我建议你“接受它”),因为它不需要学习另一个测试框架(即 PowerMock)。您是否尝试过使用 try/catch 来捕捉 TinyVM 呕吐的时间?
  • 哎呀,没关系,我的想法行不通,因为即使在 TinyVM 呕吐时捕获了异常,您仍然不会拥有您需要的东西——一个非空的 MyClass 实例。所以我最喜欢你的回答。
【解决方案4】:

@fragorl 的答案适用于静态类方法,但在实例方法上失败。 如果您需要模拟/监视实例方法,而不是抑制做替换:

PowerMockito.replace(MemberMatcher.method(Myclass.class, "setup")).with(new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] arguments) throws Throwable {
                //add some special behaviour
                //if needed can call original method afterwards in that way:
                //method.invoke(o, arguments);
                return null;
            }
        });

【讨论】:

    猜你喜欢
    • 2019-07-28
    • 1970-01-01
    • 2011-03-06
    • 2012-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-20
    相关资源
    最近更新 更多