【问题标题】:Can I mock a superclass's constructor with Mockito/Powermock?我可以用 Mockito/Powermock 模拟超类的构造函数吗?
【发布时间】:2012-04-14 23:20:28
【问题描述】:

是否可以使用 Mockito 和可选的 Powermock 来模拟超类 S,以便模拟对超类的任何调用 S(包括对 S() 构造函数的调用)?因此,使用下面的示例,如果我使用 Mockito 将 S 替换为 MockS,那么对 super() 的调用是否会使用 MockS 中的构造函数?

class S {
   S() {
      // Format user's hard drive, call 911, and initiate self-destruct
   }
}

class T extends S {
   T() {
      super();
   }
}

class Test {
   @Mock private S mockS;
   new T(); // T's call to super() should call the mock, not the destructive S.
}

我在S 中看到了有关模拟单个方法或仅模拟对super() 的调用的问题,并且读到这是不受支持的,但尚不清楚我是否可以模拟整个超类。

在我当前的测试中,当我尝试模拟 S 时,Tsuper() 的调用调用的是真正的实现,而不是模拟。

【问题讨论】:

  • 你的 mock 如何调用 super()?你能提供一个代码示例吗?
  • @nansen 我不是从模拟中调用super(),模拟是超类。请查看我的编辑。
  • 我不太喜欢S 的那门课。
  • @DerMike 为什么你认为我想嘲笑它?我只能调用一次。 :)

标签: java inheritance mockito superclass powermock


【解决方案1】:

为了解决这个明显的限制,我重构了我的代码replacing inheritance with delegation,我认为我最终得到了一个更好的设计,因为继承并不是真正必要的。

新代码如下所示。请注意,问题的代码已被简化,因此真正的类具有更多功能。

class S {
   S() {
      // Format user's hard drive, call 911, and initiate self-destruct
   }
}

class T {
   T(S s) {} // Now T "has an S" instead of "is an S"
}

class Test {
   @Mock private S mockS;
   new T(s); // T's call to super() should call the mock, not the destructive S.
}

对于那些感兴趣的人,使用 Guice 和 Android,测试看起来更像这样:

class T {
   T(Activity activity, S s) {}
}

class Test {
  @Mock Activity activity;
  @Mock S mockS;
  injector = Guice.createInjector(new AbstractModule() {
     @Override protected void configure() {
        bind(Activity.class).toInstance(activity);
        bind(S.class).toInstance(mockS);
     }}
  );
  T t = injector.getInstance(T.class);
}

【讨论】:

  • 嗨,我正在尝试实现类似于这里描述的东西,我正在测试这个,但它在 BindEverythingElse() 上显示编译错误,不确定这个类来自哪里,我包括 guice-2.0 依赖项。我是否需要添加任何其他额外的依赖项或者我错过了什么。谢谢!!
  • @Rishi 如果您没有其他自定义绑定,只需删除该行。事实上,它与答案无关,所以我将其删除。
【解决方案2】:

我认为只有在子类的方法与超类的方法不同的情况下,PowerMock 才有可能实现这一点(即,如果子类覆盖该方法,则无法模拟父方法)。更多细节可以看the relevant bug report

对于 PowerMock,请查看Suppressing Unwanted Behavior page,看看它是否足以满足您的需求。


经过大量挖掘,我最终使用JMockit 处理这些棘手的情况。在我继续使用 JMockit 之前,我尝试使用抑制将所有抛出异常的地方存根。最后,我需要重写一些方法,而不仅仅是压制它们,所以我最终放弃了它。

Android 案例使用示例:

首先,您使用@MockClass 注解模拟您的超类:

@MockClass(realClass = Activity.class, instantiation = PerMockedInstance)
public class FakeActivity {
    public Bundle mSavedInstanceState;

    @Mock
    public void $init() {}

    @Mock
    public void onCreate(Bundle savedInstanceState) {
        mSavedInstanceState = savedInstanceState;
    }
}

激活后,该类会将Activity的默认构造函数替换为$init(),并将onCreate方法替换为上述方法。对于 android,被测单元是从 Activity 派生的(在我的示例代码中,它是 HelloTestActivity)。测试类如下所示:

public class HelloTestActivityTest3 extends AndroidTest {
    @Tested
    HelloTestActivity activity;

    FakeActivity fakeActivity = new FakeActivity();

    @Before
    public void setupMocks()
    {
        Mockit.setUpMock(fakeActivity);
    }

    @Test
    public void onCreate_bundle(@Mocked Bundle savedInstanceState)
    {
        // Try to access out-of-band information from the fake
        activity.onCreate(savedInstanceState);
        assertSame(savedInstanceState, fakeActivity.mSavedInstanceState);
    }
}

调用Mockit.setupMock(fakeActivity) 用我的假实例替换了超类。通过这种用法,您也可以访问假类的内部状态。如果您不需要使用自定义功能覆盖任何方法,则可以使用 Mockit 类中提供的其他方法。

正如 rogerio 在下面的 cmets 中指出的那样,模拟 Activity 类是最低限度的。下面的代码演示了这一点。

public class HelloTestActivityTest4 {
    @Tested
    HelloTestActivity activity;

    @Mocked
    Activity base;

    @Test
    public void testOnCreate() throws Exception {
        // Just make sure "Stub!" exception is not thrown.
        activity.onCreate(null);
    }
}

声明@Mocked Activity base; 导致Activity 类及其超类的所有方法(静态初始化程序除外)在HelloActivityTest4 中定义的测试中被模拟。

【讨论】:

  • 这可能是我的误解,但我说的是模拟整个超类,而不仅仅是一种方法。查看我的编辑并告诉我。
  • 我使用 JMockit 模拟了“假”的 android Activity 类,其中每个方法都会抛出异常“Stub!”从JVM调用时。您可以模拟超级输出,或将某些方法替换为执行快捷方式的假货并模拟其余方法,删除构造函数或静态初始化程序块等。PowerMock 不具备灵活性。但我会阅读修改,并在必要时修改我的答案。
  • 我们都是因为 Android 而使用 Powermock 吗? :) 我已经模拟了Activity(我用 Roboguice 注入它们并将Activity.class 绑定到实例mockActivity)。在这个 SO 帖子的情况下,我正在测试对 ArrayAdapter 的扩展,并将其推广到问题中的 S。为了成功,我最终用委托替换了继承,然后模拟了注入的委托。
  • @glenviewjeff 我认为我们可以。我想我不会编写需要这些高级测试框架服务的臭代码。 :) 出于好奇,Roboguice 可以注入一个超类,还是我误解了你的评论?
  • 所以首先顺便说一句,在这种情况下,是 Guice 自己进行注入,而不是 Roboguice 部分。不,我通过用委托替换继承绕过了对 super 的调用,这意味着 T 不再扩展 S 而是在其构造函数中接受委托 S。是的,当我绑定它时,Guice 会自动提供这个。我会发布一个不被接受的自我回答,说明我做了什么。
【解决方案3】:

您可以做的是将超类构造函数中的“危险”代码提取到非私有方法中,然后对您的类 T 使用 Mockito spy 并覆盖该提取方法中的行为。

这当然会违反封装。 Guava 针对这种情况提供了VisibleForTesting 注解。

【讨论】:

  • 使用 PowerMockito 你有 PowerMockito.whenNew(S.class).withNoArguments().thenReturn(mockedS);当您显式调用 new S() 但不适用于 super() 时,此方法有效。格式化了我的硬盘;)
  • 我最终用委托替换了继承,然后模拟了委托。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多