如何在模拟与外部资源的连接时测试 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);
    }
}