【问题标题】:How do I mock invocations to methods in super class using jMockit如何使用 jMockit 模拟对超类中方法的调用
【发布时间】:2017-09-28 17:12:18
【问题描述】:

我有一个场景,我必须在父类中模拟一个方法。该方法是从被测方法调用的。我无法使用 jMockit 模拟该函数。

我的超类是方法如下

public abstract class SuperClass {

    protected void emailRecipients(List<String> recipients) {
       // Email recipients code. I want to mock this function.
    }
}

我的子类如下

public class MyClass extends SuperClass {
   public void methodUnderTest(HttpServletRequest request) {

    // Some code here.
    List<String> recipients = new ArrayList<>();
    recipients.add("foo@example.com");
    recipients.add("bar@example.com");

    // This needs to be mocked.
    this.emailRecipients(recipients);
  }
}

我尝试过使用partial mocks using jMockit's tutorial,但它对我不起作用。我的测试方法如下。

更新:我执行了 Rogerio 的建议如下。实现仍然调用真正的方法。当我在 Eclipse 中调试模拟类的实例时,这是我看到的 com.project.web.mvc.$Subclass_superClass@6b38c54e

@Test
public void testMethodUnderTest(@Mocked final SuperClass superClass) throws Exception {
     final MyClass myClass = new MyClass();
     new Expectations(myClass) {{
         // .. Other expectations here
         superClass.emailRecipients((List<String>) any);
     }};
     MockHttpServletRequest req = new MockHttpServletRequest();
     myClass.methodUnderTest(req);
}

问题是当我尝试模拟emailRecipients 的调用时,它总是尝试调用实际的函数。我正在使用 Java 7、jMockit v1.35 和 Maven 3x 进行构建。

更新代码是旧代码。因此,我们无法更新它。我们不能使用 PowerMock,因为它不在公司批准的库中。我们可以使用 jMockit 或 Mockito 或两者的组合。

【问题讨论】:

  • 如果需要模拟SuperClass,则声明为@Mocked。就这么简单。
  • 如果这个遗留代码在未来根本不需要改变,你需要单元测试做什么?
  • 我正在编写功能测试。
  • @Rogerio 我需要模拟超类中的函数,而不是超类本身。
  • 正如我在最初的评论中提到的,声明一个@Mocked SuperClass mock 字段/参数;然后测试可以记录/验证对模拟超类方法的期望。

标签: java unit-testing jmockit


【解决方案1】:

您想从父类模拟方法的事实表明您的方法失败了关注点分离/单一责任模式 (SoC/SRP)。

按照 Rajiv Kapoor 的建议使用 PowerMock 是可能的,但这(就像任何使用 PowerMock 一样)将放弃糟糕的设计。

您可以通过应用 优先组合优于继承 原则 (FCoI) 来解决您的设计问题。

为此,您需要将(最有可能的)抽象超类更改为“普通”类。您将创建一个接口,在您的超类中声明所有publicabstract 方法。您的子类将不再扩展父类,而是实现接口。它将获得前父类的一个实例作为 dependency 并根据需要调用它的方法来提供常见行为。

无需PowerMock即可轻松模拟此依赖项。


更新代码是遗留代码。因此,我们无法更新它。

在这种情况下,你就被排除在外了。

您拥有的代码不是unittestable,因为它是以不可测试的方式编写的。您唯一的机会是编写 module 和/或 acceptance 测试(不使用模拟框架)覆盖代码中的每个执行路径。

此测试的创建成本高且速度慢,但它们会在以后将代码重构为可测试(== 可更改)的东西时保护您。

【讨论】:

  • 代码是遗留代码。如果不破坏很多用例,就无法对其进行重构。代码不能改。d
  • 在这种情况下,重构可能会很危险。但是使用 PowerMock 将有效地消除以后改进代码的任何机会。这是因为“PowerMock 测试”将在重构时中断,因此无法保护您保留所需的行为。
【解决方案2】:

见下例

附:使用 Mockito.any(HttpServletRequest.class) 而不是 Mockito.any(ArrayList.class) 为您的代码

超级类

public abstract class SuperClass {

    protected void emailRecipients(List<String> recipients) {
       System.out.println("Emailed!");
    }
}

我的班级

public class MyClass extends SuperClass {
    public void methodUnderTest() {

        // Some code here.
        ArrayList<String> recipients = new ArrayList<>();
        recipients.add("foo@example.com");
        recipients.add("bar@example.com");

        // This needs to be mocked.
        this.emailRecipients(recipients);
    }
}

测试类

public class TestCase {

    MyClass myClass = Mockito.mock(MyClass.class, Mockito.CALLS_REAL_METHODS);

    @Before
    public void prepare() {

        PowerMockito.doNothing().when(myClass).emailRecipients(Mockito.any(ArrayList.class));

        /*PowerMockito.doAnswer(new Answer<Void>() {

        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            System.out.println("Custom code");
            return null;
        }
        }).when(myClass).emailRecipients(Mockito.any(ArrayList.class));*/
    }

    @Test
    public void testMethodUnderTest() throws Exception {

        myClass.methodUnderTest();
    }
}

如果您不希望执行 emailRecipients 中的代码,请使用 doNothing() else 使用 doAnswer 执行一些其他代码

【讨论】:

  • 我们不能使用 PowerMock。它不在公司已清除的第三方库中。我们只能使用 jMockit。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-17
相关资源
最近更新 更多