enbdk
enbdk

Reputation: 31

Nested configuration not working as expected

I'm working on a project using spring boot, and we've just upgraded to version 1.4.0.RELEASE. As part of the version upgrade, we've started using the @SpringBootTest annotation on our abstract integration test class. After reading the documentation, it sounds like we should be able to use a nested @TestConfiguration-annotated config class to override bean definitions during specific tests. This is not working for us, and instead the non-test bean we are trying to override is still being used.

Interestingly, it seems like usages of the mock test bean and the production bean are actually intertwined within the same test, as if both beans exist side by side in the application context. Also, it seems like the order in which the integration tests run somehow affects this behaviour. I'm wondering if this is something we've misconfigured, or if there is something else going on.

edit:

The abstract class that the integration tests inherit from looks like this:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles({"local", "test"})
public abstract class BaseIntegrationTest {

    @Value("${local.server.port}")
    protected int port;
}

The integration test that we are seeing the strange behaviour in looks like this:

public class WebhookProcessorIT extends BaseIntegrationTest {

    @TestConfiguration
    public static class Config {
        @Bean
        @Primary
        public WebhookTask webhookTask() {
            return mock(WebhookTask.class);
        }
    }

    // sometimes the mock above is used and sometimes
    // the actual production bean is used
    @Autowired
    private WebhookTask task;

    @Before
    public void setup() {
        when(task.process(any())).thenReturn(true);
    }

    // tests ...
}

And this is what the root application context class looks like:

@EnableAutoConfiguration(exclude = ErrorMvcAutoConfiguration.class)
@SpringBootApplication
public class Application {
    private static final Logger log = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) {
        final SpringApplication app = new SpringApplication(Application.class);
        app.setBannerMode(Banner.Mode.OFF);
        app.run(args);
    }
}

edit:

I have also tried using @MockBean, like so:

public class WebhookProcessorIT extends BaseIntegrationTest {

    @MockBean
    private WebhookTask task;

but I get the same result when the tests run. I can see that Spring is trying to override the production bean with the mock I am providing when I look at the logs during the test setup:

build   15-Sep-2016 09:09:24    2016-09-15 09:09:24 [34mINFO [0;39m [36m[DefaultListableBeanFactory][0;39m (main) Overriding bean definition for bean 'productionWebhookTask' with a different definition

however when it comes to test execution, i can still see the production bean being used:

build   15-Sep-2016 09:09:29    2016-09-15 09:09:29 [39mDEBUG[0;39m [36m[WebhookSupplier][0;39m (WebhookProcessor) Received webhook with ID '1234' from queue.
build   15-Sep-2016 09:09:30    2016-09-15 09:09:30 [39mDEBUG[0;39m [36m[WebhookSupplier][0;39m (WebhookProcessor) Received webhook with ID '5678' from queue.
build   15-Sep-2016 09:09:30    2016-09-15 09:09:30 [39mDEBUG[0;39m [36m[ProductionWebhookTask][0;39m (WebhookProcessor) Received webhook with ID '1234' for processing // production webhook task bean still being used for webhook '1234'
build   15-Sep-2016 09:09:30    2016-09-15 09:09:30 [39mDEBUG[0;39m [36m[WebhookSupplier][0;39m (WebhookProcessor) Deleting webhook with id '5678' from queue. // mock bean causes production logic to be skipped and we just delete webhook '5678'
// More logs from production webhook task operating on webhook with id '1234' and causing the test to fail

Upvotes: 3

Views: 2264

Answers (2)

Shady Ragab
Shady Ragab

Reputation: 725

Since you use Spring Boot version 1.4.0, you can go on use the new introduced annotation @MockBean instead of using different configuration classes to mock your original beans. It's straight forward and very suitable to your use case.

Here you go with an example from the documentation

Upvotes: 0

Shady Ragab
Shady Ragab

Reputation: 725

Anyway, you can annotate your test beans with @Profile("test") and your real ones with @Profile("production")

and in your properties file put the property spring.profiles.active=test

from documentation

Unlike regular @Configuration classes the use of @TestConfiguration does not prevent auto-detection of @SpringBootConfiguration.

Unlike a nested @Configuration class which would be used instead of a your application’s primary configuration, a nested @TestConfiguration class will be used in addition to your application’s primary configuration.

Upvotes: 0

Related Questions