javaspring-bootspring-boot-test

Spring Boot 3.4: @MockitoSpyBean causes NullPointerException during @PostConstruct


I'm currently migrating a Spring Boot app to version 3.4.

In this version, @MockBean and @SpyBean are deprecated and replaced by @MockitoBean and @MockitoSpyBean

I've made the changes in my class annotated with @TestConfiguration, and since then my SpringBootTests are failing at the initialization phase.

This is how my configuration class looks like:

@TestConfiguration
public class IntegrationTestConfig {

    @MockitoSpyBean
    private LanguageRepository languageRepository;

    @PostConstruct
    void setUp() {
        when(languageRepository.findAllCodes()).thenReturn(List.of("fr", "en"));
    }
}

This configuration is included via a custom meta-annotation, like this:

@Retention(RetentionPolicy.RUNTIME)
@SpringBootTest(classes = {IntegrationTestConfig.class, SpringSecurityTestConfig.class})
@ActiveProfiles("test")
@Sql(scripts = "classpath:sql/clearTables.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
@AutoConfigureMockMvc
public @interface IntegrationTest {

}

Since making this change, I now encounter the following error during @PostConstruct execution: java.lang.NullPointerException: Cannot invoke "package.whatever.LanguageRepository.findAllCodes()" because "this.languageRepository" is null

The error does not occur if I revert to the deprecated API (@SpyBean). Everything works fine with the old annotations.

My questions:


Edit: Giving context

This @SpyBean is used to mock something only done at application startup where an external WS call is made.

@Bean
ApplicationRunner fetchTranslations(LocalazyService translationService) {
    log.info("Initializing translations...");
    return ignored -> translationService.updateTranslationsWithCdn();
}

Solution

  • So, as of late, I haven't found any solution similar to the @PostConstruct one.

    In the end, here's how I made it work without inheritance or @BeforeEach setups:

    This is what the custom IntegrationTest annotation looks like:

    @Retention(RetentionPolicy.RUNTIME)
    @SpringBootTest(classes = SpringSecurityTestConfig.class)
    @ActiveProfiles("test")
    @Sql(scripts = "classpath:sql/clearTables.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
    @AutoConfigureMockMvc
    @EnableWireMock({
        @ConfigureWireMock(name = "localazy-client", baseUrlProperties = "i18n.localazy.cdnUrl", filesUnderClasspath = "wiremock/localazy-client")
    })
    @MockitoSpyBean(types = {JavaMailSender.class, LocalazyService.class})
    public @interface IntegrationTest {
    
    }
    

    Using Wiremock is a bit heavier than I would have liked, and I might lose a few seconds when running tests individually, but it's a compromise I can accept.

    I don't need the IntegrationTestConfig configuration class anymore, as it's now empty.