1

I want to test the following class, that is a spring boot with CommandLineRunner, if it receives the flag compaction.manually.triggered = true for specific TaskMode. I suppose that I need to instantiate the whole Spring application runner during the test (based on this answer). However, I don't want to connect to external resources (was in my case). So I think I need to mock it as well.

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

I wrote the test below and my commandLineRunner is Null when I use @ExtendWith(MockitoExtension.class). I understand that I cannot use @ExtendWith(MockitoExtension.class) and want that Spring initializes my context. So I tried to switch to @SpringBootTest. Then spring initializes my context but it tries to connect to AWS. So, I don't know to mock the connection to AWS while using @SpringBootTest to initialize the spring context during test.

// @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();
    }
}
Felipe
  • 7,013
  • 8
  • 44
  • 102
  • 2
    Why? Just write a unit test for this, you don't need Spring for that. Just create an instance of `MyRunner` yourself and inject the mocks and values you want. – M. Deinum Feb 10 '22 at 09:50
  • that is true. I was overcomplicating it. thanks! – Felipe Feb 10 '22 at 10:25

1 Answers1

1

As @M. Deinum said, it is not necessary to start the Spring context to test what I wanted. I will post the solution here just as a reference. I am also using the system-lambda dependency to catch System.exit(-1).

<dependency>
   <groupId>com.github.stefanbirkner</groupId>
   <artifactId>system-lambda</artifactId>
</dependency>

Unit test with only 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);
    }
}
Felipe
  • 7,013
  • 8
  • 44
  • 102