我有一个Java应用程序(里面没有Spring(,我想通过集成测试进行测试。
我的主要用例是主函数,它使用指定的输入在数据库上执行一些操作,并将一些请求发送到两个不同的服务,一个 SOAP 和一个 REST。
现在我有一个有效的 JUnit 配置(在单元和集成测试中拆分(+ io.fabric8:docker-maven-plugin 在集成测试期间为数据库使用 docker 映像。
我要做的是为这两个服务添加一个模拟,特别是用于直接调用外部服务的方法。
最大的问题是我有这个结构:
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和/或更改方法sendNotification()
的行为。
实际问题:现在使用 Mockito 和 PowerMock 我遇到了一个问题,即我无法使用 FakeNotifierHandler 全局和直接更改类 NotifierHandler。同样试图改变方法的行为。
特别是,我需要的是创建一个
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]
...
}
}
阅读我尝试过的所有示例,我只看到了更改方法返回值的简单示例,而不是一个类的一个方法的行为,另一个类使用另一个方法的行为,我将其用作集成测试的起点。
注意:可能有一种快速的方法可以做到这一点,但我对这种类型的测试(Mockito,PowerMock,...(非常陌生,我没有找到这种特殊奇怪情况的例子。
编辑:与如何使用PowerMockito模拟构造函数不同,因为我需要更改方法的行为,而不仅仅是返回值。
提前非常感谢
我找到了一个效果很好且非常简单的解决方案!
解决方案是 PowerMock (https://github.com/powermock/powermock(,特别是将类的实例的创建替换为另一个:https://github.com/powermock/powermock/wiki/mockito#how-to-mock-construction-of-new-objects
我的项目中只有一个问题,那就是JUnit 5。PowerMock支持JUnit 4,因此,只有解决方案的某些测试才使用它。 为了做到这一点,需要更换
import org.junit.jupiter.api.Test;
跟
import org.junit.Test;
为了使用">whenNew(("方法,我扩展了测试中必须替换的类,并且我只覆盖了集成测试所需的方法。 此解决方案的最大好处是我的代码保持不变,我也可以在旧代码上使用此方法,而不会在代码重构期间引入回归的风险。
关于集成测试的代码,这里有一个例子:
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");
}
}
这里是绒球的配置.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>
我认为您最头疼的是您在类A
、B
和NotifierHandler
之间具有紧密耦合的依赖关系。我会从以下方面开始:
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
。
我希望这对你有所帮助。