如何在模拟与外部资源的连接时测试 Spring CommandLineRunner?
How to test a Spring CommandLineRunner while I am mocking the connection to an external resources?
我想测试以下 class,这是一个 spring 引导 CommandLineRunner
,如果它收到标志 compaction.manually.triggered
= true
具体TaskMode
。我想我需要在测试期间实例化整个 Spring 应用程序运行程序 (based on this answer)。但是,我不想连接到外部资源(就我而言)。所以我想我也需要模拟它。
@Component
public class MyRunner implements CommandLineRunner {
private final MyTask myTask;
private final CompactionMode mode;
private final boolean manuallyTriggered;
@Autowired
public MyRunner(
MyTask myTask,
@Value("${task.mode}") TaskMode mode,
@Value("#{new Boolean('${compaction.manually.triggered:false}')}") boolean manuallyTriggered
) {
Preconditions.checkNotNull(mode, "Compaction mode was not specified correctly");
this.mode = mode;
this.manuallyTriggered = manuallyTriggered;
this.myTask = myTask;
}
@Override
public void run(final String... args) {
try {
Preconditions.checkNotNull(mode, "Compaction mode was not specified correctly");
LOG.info(
"Task reporting compactor in {} mode started. The task was triggered {}.",
mode,
manuallyTriggered ? "manually" : "by the conJob schedule"
);
switch (mode) {
case KPI, ANALYTICS, ANALYTICS_AGG -> {
checkManuallyTriggered(mode);
myTask.executeTask();
}
default -> throw new UnsupportedOperationException("myTask mode " + mode + " is not supported.");
}
System.exit(0);
} catch (Exception ex) {
LOG.error("Failure during mytask in {} mode", mode, ex);
System.exit(-1);
}
}
private void checkManuallyTriggered(CompactionMode mode) {
if (!manuallyTriggered) {
throw new UnsupportedOperationException(
"Reporting compactor mode " + mode.name() + " is supported only using the " + "parameter --compaction.manually.triggered=true.");
}
}
}
我在下面写了测试,当我使用 @ExtendWith(MockitoExtension.class)
时,我的 commandLineRunner
为 Null。我知道我不能使用 @ExtendWith(MockitoExtension.class)
并希望 Spring 初始化我的上下文。所以我尝试切换到@SpringBootTest
。然后 spring 初始化我的上下文,但它尝试连接到 AWS。因此,我不知道在测试期间使用 @SpringBootTest
初始化 spring 上下文时模拟与 AWS 的连接。
// @SpringBootTest // ENABLE THIS TO TEST THE CommandLineRunner
@ExtendWith(MockitoExtension.class) // ENABLE THIS TO MOCK THE SERVICES CONNECTION TO AWS
class ReportingCompactorRunnerTest {
AthenaProperties athenaProperties = new AthenaProperties(
"com.simba.athena.jdbc42.Driver",
"eu-central-1",
"database",
"test_reports"
);
private AthenaDirectConnection athenaDirectConnection = mock(AthenaDirectConnection.class, Mockito.RETURNS_DEEP_STUBS);
private MockSettings mockSettings = withSettings().useConstructor(athenaDirectConnection, athenaProperties);
private AthenaImportDDLService athenaImportDDLService = mock(AthenaImportDDLService.class, mockSettings);
@Mock
private MyTask myTask;
@SpyBean // using this annotation to spy the arguments on the CommandLineRunner
private ReportingCompactorRunner commandLineRunner;
@DynamicPropertySource
static void containerProperties(DynamicPropertyRegistry registry) {
registry.add("compaction.mode", () -> "KPI");
registry.add("compaction.manually.triggered", () -> "false");
}
@Test
void analyticsCleanupIsTriggeredOnlyManually() throws Exception {
doNothing().when(myTask).createViewsIfNotExist(); // a call inside the task that I don't want to execute
// commandLineRunner IS NULL WHEN I USE MOCKITO
RuntimeException thrown = assertThrows(
RuntimeException.class,
() -> commandLineRunner.run()
);
// assertTrue(thrown.getMessage().contains("Reporting compactor mode ANALYTICS_CLEANUP is supported only
// using the parameter --compaction.manually.triggered=true."));
assertTrue(thrown.getMessage().contains("null"));
verify(athenaReportCompactionTask, times(1)).executeTask();
}
}
作为@M。 Deinum 说,没有必要启动 Spring 上下文来测试我想要的东西。我将post这里的解决方案作为参考。我也在使用 system-lambda dependency 来捕获 System.exit(-1)
。
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-lambda</artifactId>
</dependency>
仅使用 Mockito 进行单元测试:
@ExtendWith(MockitoExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyRunnerTest {
private AthenaImportDDLService athenaImportDDLService;
@Mock
private MyTask1 myTask;
private MyRunner commandLineRunner;
private static Stream<Arguments> compactionModeAndTriggeredFlagParameters() {
return Stream.of(
Arguments.of(CompactionMode.KPI, true, 0),
Arguments.of(CompactionMode.KPI, false, 0),
....
);
}
@BeforeAll
public void setup() {
AthenaProperties athenaProperties = new AthenaProperties("com.simba.athena.jdbc42.Driver","eu-central-1","database","test_reports");
AthenaDirectConnection athenaDirectConnection = mock(AthenaDirectConnection.class, Mockito.RETURNS_DEEP_STUBS);
MockSettings mockSettings = withSettings().useConstructor(athenaDirectConnection, athenaProperties);
athenaImportDDLService = mock(AthenaImportDDLService.class, mockSettings);
}
@ParameterizedTest
@MethodSource("compactionModeAndTriggeredFlagParameters")
void compactionModesWithManuallyTriggeredFlag(CompactionMode mode, boolean manuallyTriggered, int expectedCode)
throws Exception {
doNothing().when(athenaImportDDLService).createViewsIfNotExist();
commandLineRunner = new MyRunner(
myTask,
mode,
manuallyTriggered
);
int statusCode = catchSystemExit(() -> {
commandLineRunner.run();
});
assertEquals(expectedCode, statusCode);
}
}
我想测试以下 class,这是一个 spring 引导 CommandLineRunner
,如果它收到标志 compaction.manually.triggered
= true
具体TaskMode
。我想我需要在测试期间实例化整个 Spring 应用程序运行程序 (based on this answer)。但是,我不想连接到外部资源(就我而言)。所以我想我也需要模拟它。
@Component
public class MyRunner implements CommandLineRunner {
private final MyTask myTask;
private final CompactionMode mode;
private final boolean manuallyTriggered;
@Autowired
public MyRunner(
MyTask myTask,
@Value("${task.mode}") TaskMode mode,
@Value("#{new Boolean('${compaction.manually.triggered:false}')}") boolean manuallyTriggered
) {
Preconditions.checkNotNull(mode, "Compaction mode was not specified correctly");
this.mode = mode;
this.manuallyTriggered = manuallyTriggered;
this.myTask = myTask;
}
@Override
public void run(final String... args) {
try {
Preconditions.checkNotNull(mode, "Compaction mode was not specified correctly");
LOG.info(
"Task reporting compactor in {} mode started. The task was triggered {}.",
mode,
manuallyTriggered ? "manually" : "by the conJob schedule"
);
switch (mode) {
case KPI, ANALYTICS, ANALYTICS_AGG -> {
checkManuallyTriggered(mode);
myTask.executeTask();
}
default -> throw new UnsupportedOperationException("myTask mode " + mode + " is not supported.");
}
System.exit(0);
} catch (Exception ex) {
LOG.error("Failure during mytask in {} mode", mode, ex);
System.exit(-1);
}
}
private void checkManuallyTriggered(CompactionMode mode) {
if (!manuallyTriggered) {
throw new UnsupportedOperationException(
"Reporting compactor mode " + mode.name() + " is supported only using the " + "parameter --compaction.manually.triggered=true.");
}
}
}
我在下面写了测试,当我使用 @ExtendWith(MockitoExtension.class)
时,我的 commandLineRunner
为 Null。我知道我不能使用 @ExtendWith(MockitoExtension.class)
并希望 Spring 初始化我的上下文。所以我尝试切换到@SpringBootTest
。然后 spring 初始化我的上下文,但它尝试连接到 AWS。因此,我不知道在测试期间使用 @SpringBootTest
初始化 spring 上下文时模拟与 AWS 的连接。
// @SpringBootTest // ENABLE THIS TO TEST THE CommandLineRunner
@ExtendWith(MockitoExtension.class) // ENABLE THIS TO MOCK THE SERVICES CONNECTION TO AWS
class ReportingCompactorRunnerTest {
AthenaProperties athenaProperties = new AthenaProperties(
"com.simba.athena.jdbc42.Driver",
"eu-central-1",
"database",
"test_reports"
);
private AthenaDirectConnection athenaDirectConnection = mock(AthenaDirectConnection.class, Mockito.RETURNS_DEEP_STUBS);
private MockSettings mockSettings = withSettings().useConstructor(athenaDirectConnection, athenaProperties);
private AthenaImportDDLService athenaImportDDLService = mock(AthenaImportDDLService.class, mockSettings);
@Mock
private MyTask myTask;
@SpyBean // using this annotation to spy the arguments on the CommandLineRunner
private ReportingCompactorRunner commandLineRunner;
@DynamicPropertySource
static void containerProperties(DynamicPropertyRegistry registry) {
registry.add("compaction.mode", () -> "KPI");
registry.add("compaction.manually.triggered", () -> "false");
}
@Test
void analyticsCleanupIsTriggeredOnlyManually() throws Exception {
doNothing().when(myTask).createViewsIfNotExist(); // a call inside the task that I don't want to execute
// commandLineRunner IS NULL WHEN I USE MOCKITO
RuntimeException thrown = assertThrows(
RuntimeException.class,
() -> commandLineRunner.run()
);
// assertTrue(thrown.getMessage().contains("Reporting compactor mode ANALYTICS_CLEANUP is supported only
// using the parameter --compaction.manually.triggered=true."));
assertTrue(thrown.getMessage().contains("null"));
verify(athenaReportCompactionTask, times(1)).executeTask();
}
}
作为@M。 Deinum 说,没有必要启动 Spring 上下文来测试我想要的东西。我将post这里的解决方案作为参考。我也在使用 system-lambda dependency 来捕获 System.exit(-1)
。
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-lambda</artifactId>
</dependency>
仅使用 Mockito 进行单元测试:
@ExtendWith(MockitoExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MyRunnerTest {
private AthenaImportDDLService athenaImportDDLService;
@Mock
private MyTask1 myTask;
private MyRunner commandLineRunner;
private static Stream<Arguments> compactionModeAndTriggeredFlagParameters() {
return Stream.of(
Arguments.of(CompactionMode.KPI, true, 0),
Arguments.of(CompactionMode.KPI, false, 0),
....
);
}
@BeforeAll
public void setup() {
AthenaProperties athenaProperties = new AthenaProperties("com.simba.athena.jdbc42.Driver","eu-central-1","database","test_reports");
AthenaDirectConnection athenaDirectConnection = mock(AthenaDirectConnection.class, Mockito.RETURNS_DEEP_STUBS);
MockSettings mockSettings = withSettings().useConstructor(athenaDirectConnection, athenaProperties);
athenaImportDDLService = mock(AthenaImportDDLService.class, mockSettings);
}
@ParameterizedTest
@MethodSource("compactionModeAndTriggeredFlagParameters")
void compactionModesWithManuallyTriggeredFlag(CompactionMode mode, boolean manuallyTriggered, int expectedCode)
throws Exception {
doNothing().when(athenaImportDDLService).createViewsIfNotExist();
commandLineRunner = new MyRunner(
myTask,
mode,
manuallyTriggered
);
int statusCode = catchSystemExit(() -> {
commandLineRunner.run();
});
assertEquals(expectedCode, statusCode);
}
}