繁体   English   中英

JUnit 更改 class 或集成测试中的方法行为

[英]JUnit change class or method behaviour in integration tests

我有一个 java 应用程序(内部没有 Spring),我想用集成测试进行测试。

我的主要用例是主要的 function,它通过指定的输入在数据库上做一些事情并向两个不同的服务发送一些请求,一个是 SOAP,一个是 REST。

现在我有一个有效的 JUnit 配置(在单元和集成测试中拆分)+ io.fabric8:docker-maven-plugin 在集成测试期间使用 Z05B6053C41A2130AFD6FC3B158BDAE4 图像数据库。

我要做的是为这两个服务添加一个模拟,特别是用于直接调用外部服务的方法。

最大的问题是我有这个结构:

class A{
    Result mainFunction(Request r){
        ....
        B b = new B(params);
        b.logEvent(someParameters)
        ....
    }
}
class B{
    int logEvent(Object someParameters){
        ....
        NotifierHandler nh = new NotifierHandler(param1);
        nh.sendNotification(json);
        ....
    }
}

我在哪里:

class NotifierHandler{
    String sendNotification(Json j){
        ...
        [call to REST service with some parameters]
        ...
        ...
        [call to SOAP service with some parameters]
        ...
    }
}

我需要的是:调用A.mainFunction(r)在测试环境中,用 FakeNotifierHandler 替换 NotifierHandler 和/或更改方法sendNotification()的行为。

实际问题:使用 Mockito 和 PowerMock 现在我遇到的问题是我无法全局直接更改 class NotifierHandler 和 FakeNotifierHandler。 同样试图改变方法的行为。

特别是,我需要创建一个

class FakeNotifierHandler{
    String sendNotification(Json j){
        ...
        [save on an HashMap what I should send to the REST service]
        ...
        ...
        [save on another HashMap what I should send to the SOAP service]
        ...
    }
}

阅读我尝试过的所有示例,我只看到了更改方法的返回值的简单示例,而不是一个方法的行为,一个 class 被另一个使用,另一个我用作集成测试的起点。

注意:可能有一种快速的方法可以做到这一点,但我对这种类型的测试(Mockito,PowerMock,...)非常陌生,而且我没有找到这种特殊奇怪案例的示例。

编辑:与如何使用 PowerMockito 模拟构造函数不同,因为我需要更改方法的行为,而不仅仅是返回值。

非常感谢提前

我找到了一个效果很好的解决方案,而且非常简单!

The solution is PowerMock ( https://github.com/powermock/powermock ) and in particular replace the creation of an instance of a class with another: https://github.com/powermock/powermock/wiki/mockito#how-模拟新对象的构造

我的项目中只有一个问题,它是 JUnit 5. PowerMock 支持 JUnit 4,因此,仅用于解决方案的一些测试正在使用它。 为了做到这一点,需要更换

import org.junit.jupiter.api.Test;

import org.junit.Test;

为了使用“ whenNew() ”方法,我扩展了测试中必须替换的 class,并且我只覆盖了集成测试所需的方法。 这个解决方案的最大好处是我的代码没有受到影响,我也可以在旧代码上使用这种方法,而不会有在代码重构期间引入回归的风险。

关于集成测试的代码,这里有一个例子:

import org.junit.jupiter.api.DisplayName;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PowerMockIgnore({"javax.crypto.*" }) // https://github.com/powermock/powermock/issues/294
@PrepareForTest(LegacyCoreNetworkClassPlg.class) // it is the class that contains the "new SOAPCallHelper(..)" code that I want to intercept and replace with a stub
public class ITestExample extends InitTestSuite {
    @Test
    @DisplayName("Test the update of a document status")
    public void iTestStubLegacyNetworkCall() throws Exception {

        // I'm using JUnit 4
        // I need to call @BeforeAll defined in InitTestSuite.init();
        // that works only with JUnit 5
        init();

        LOG.debug("IN stubbing...");
        SOAPCallHelperStub stub = new SOAPCallHelperStub("empty");
        PowerMockito.whenNew(SOAPCallHelper.class).withAnyArguments().thenReturn(stub);
        LOG.debug("OUT stubbing!!!");

        LOG.debug("IN iTestStubLegacyNetworkCall");
        ...
        // Here I can create any instance of every class, but when an instance of 
        // LegacyCoreNetworkClassPlg.class is created directly or indirectly, PowerMock
        // is checking it and when LegacyCoreNetworkClassPlg.class will create a new
        // instance of SOAPCallHelper it will change it with the 
        // SOAPCallHelperStub instance.
        ...
        LOG.debug("OUT iTestStubLegacyNetworkCall");
    }
}

这里配置pom.xml

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <junit.jupiter.version>5.5.2</junit.jupiter.version>
    <junit.vintage.version>5.5.2</junit.vintage.version>
    <junit.platform.version>1.3.2</junit.platform.version>
    <junit.platform.engine.version>1.5.2</junit.platform.engine.version>
    <powermock.version>2.0.2</powermock.version>

    <!-- FOR TEST -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>${junit.jupiter.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- Only required to run tests in an IDE that bundles an older version -->
    <dependency>
        <groupId>org.junit.platform</groupId>
        <artifactId>junit-platform-launcher</artifactId>
        <version>${junit.platform.version}</version>
        <scope>test</scope>
    </dependency>
    <!-- Only required to run tests in an IDE that bundles an older version -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>${junit.jupiter.version}</version>
        <scope>test</scope>
    </dependency>
    <!-- Only required to run tests in an IDE that bundles an older version -->
    <dependency>
        <groupId>org.junit.vintage</groupId>
        <artifactId>junit-vintage-engine</artifactId>
        <version>${junit.vintage.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.platform</groupId>
        <artifactId>junit-platform-engine</artifactId>
        <version>${junit.platform.engine.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-params</artifactId>
        <version>${junit.vintage.version}</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>${powermock.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito2</artifactId>
        <version>${powermock.version}</version>
        <scope>test</scope>
    </dependency>

我认为在您的情况下,主要令人头疼的是您在 class ABNotifierHandler之间存在紧密耦合的依赖关系。 我将从:

class A {
  private B b;

  public A(B b) {
    this.b = b;
  }

  Result mainFunction(Request r){
      ....
      b.logEvent(someParameters)
      ....
  }
}
class B {
  private NotifierHandler nh;

  public B(NotifierHandler nh) {
    this.nh = nh;
  }

  int logEvent(Object someParameters){
      ....
      nh.sendNotification(json);
      ....
  }
}

使 NotifierHanlder 成为一个接口:

interface NotifierHandler {
  String sendNotification(String json);
}

并进行两种实现:一种用于真实用例,另一种用于您可以随意存根的假的:

class FakeNotifierHandler implements NotifierHandler {

  @Override
  public String sendNotification(String json) {
    // whatever is needed for you
  }
}

在您的测试中注入FakeNotifierHandler

我希望这可以帮助你。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM