简体   繁体   English

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

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

I have a java application (no Spring inside) that I want to test with an integration test.我有一个 java 应用程序(内部没有 Spring),我想用集成测试进行测试。

My main use case is the main function that with a specified input do some things on the database and send some request to two different services, one SOAP and one REST.我的主要用例是主要的 function,它通过指定的输入在数据库上做一些事情并向两个不同的服务发送一些请求,一个是 SOAP,一个是 REST。

Now I have a working JUnit configuration (splitted in unit and integration tests) + io.fabric8:docker-maven-plugin that use a docker image for the database during integration tests.现在我有一个有效的 JUnit 配置(在单元和集成测试中拆分)+ io.fabric8:docker-maven-plugin 在集成测试期间使用 Z05B6053C41A2130AFD6FC3B158BDAE4 图像数据库。

What I'm trying to do is to add a mock for these 2 services, in particular, the method that is used to call directly the external service.我要做的是为这两个服务添加一个模拟,特别是用于直接调用外部服务的方法。

The big problem is that I have this structure:最大的问题是我有这个结构:

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);
        ....
    }
}

where I have:我在哪里:

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

What I need : call A.mainFunction(r) having, in the test environment, replaced the NotifierHandler with a FakeNotifierHandler and/or change the behaviour of the method sendNotification() .我需要的是:调用A.mainFunction(r)在测试环境中,用 FakeNotifierHandler 替换 NotifierHandler 和/或更改方法sendNotification()的行为。

Actual problems : Using Mockito and PowerMock now I have the problem that I'm not able to change globally and directly the class NotifierHandler with FakeNotifierHandler.实际问题:使用 Mockito 和 PowerMock 现在我遇到的问题是我无法全局直接更改 class NotifierHandler 和 FakeNotifierHandler。 The same trying to changing the behaviour of the method.同样试图改变方法的行为。

In particular, what I need is to create a特别是,我需要创建一个

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]
        ...
    }
}

Reading all example that I tryed I saw only simple examples that change the return value of a method and not the behaviour of one method of one class used by another and another that I'm using as the start point of the integration test.阅读我尝试过的所有示例,我只看到了更改方法的返回值的简单示例,而不是一个方法的行为,一个 class 被另一个使用,另一个我用作集成测试的起点。

NOTE: probably there is a fast way to do this but I'm very new on this type of tests (Mockito, PowerMock,...) and I have found no example for this particular strange case.注意:可能有一种快速的方法可以做到这一点,但我对这种类型的测试(Mockito,PowerMock,...)非常陌生,而且我没有找到这种特殊奇怪案例的示例。

EDIT: not similar to How to mock constructor with PowerMockito because I need to change the behaviour of the method, not only the return value.编辑:与如何使用 PowerMockito 模拟构造函数不同,因为我需要更改方法的行为,而不仅仅是返回值。

Thanks a lot in advance非常感谢提前

I found a solution that works very well and it is very simple!我找到了一个效果很好的解决方案,而且非常简单!

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-to-mock-construction-of-new-objects 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-模拟新对象的构造

There is only one problem in my project and it is JUnit 5. PowerMock support JUnit 4 and for this reason, only for some tests of the solution are using it.我的项目中只有一个问题,它是 JUnit 5. PowerMock 支持 JUnit 4,因此,仅用于解决方案的一些测试正在使用它。 In order to do this there is the needed to replace为了做到这一点,需要更换

import org.junit.jupiter.api.Test;

with

import org.junit.Test;

In order to use teh " whenNew() " methods I had extented the class that in tests must be replaced and I have overwritten only methods that are necessary for the integration test.为了使用“ whenNew() ”方法,我扩展了测试中必须替换的 class,并且我只覆盖了集成测试所需的方法。 The big benefit of this solution is that my code is untouched and I can use this approach also on old code without the risk of introducing regressions during the refactor of the code.这个解决方案的最大好处是我的代码没有受到影响,我也可以在旧代码上使用这种方法,而不会有在代码重构期间引入回归的风险。

Regarding the code of a integration test, here an example:关于集成测试的代码,这里有一个例子:

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");
    }
}

Here the configuration of the pom.xml这里配置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>

I think the main headache in your case is that you have tightly coupled dependencies between class A , B and NotifierHandler .我认为在您的情况下,主要令人头疼的是您在 class ABNotifierHandler之间存在紧密耦合的依赖关系。 I would start with:我将从:

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);
      ....
  }
}

Make NotifierHanlder an interface:使 NotifierHanlder 成为一个接口:

interface NotifierHandler {
  String sendNotification(String json);
}

and make two implementations: one for a real use case, and one fake that you can stub whatever you want:并进行两种实现:一种用于真实用例,另一种用于您可以随意存根的假的:

class FakeNotifierHandler implements NotifierHandler {

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

Inject FakeNotifierHandler in your test.在您的测试中注入FakeNotifierHandler

I hope this helps you.我希望这可以帮助你。

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

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