Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring boot test with multiple configuration

In my Spring boot 2.1 project I have different @Configurations for different test (ConfigurationA and ConfigurationB), that reside in different packages. Both configurations define the same set of beans but in a different manner (mocked vs. the real thing)

As I am aware of the Bean overriding mechanism introduced in Spring Boot 2.1, I have set the property: spring.main.allow-bean-definition-overriding=true.

However I do have a test with the following the setup of the following configuration and test class. First there is a @Configuration in the productive part (I'm using Maven):

package com.stackoverflow;

@Configuration
public class ProdConfiguration{
...
}

Then in the test branch there is a general Test @Configuration on the same package level:

package com.stackoverflow

@Configuration
public class TestConfiguration {
  @Bean
  public GameMap gameMap() {
    return Mockito.mock(GameMap.class);
  }
}

And in a subpackage I have another @Configuration:

package com.stackoverflow.impl;

@Configuration
public class RealMapTestConfiguration {
  @Bean
  public GameMap gameMap() {
    return new GameMap("testMap.json");
  }
}

And then of course there is the test that is troubling me:

package com.stackoverflow.impl;

@ExtendWith(SpringExtension.class)
@SpringBootTest
@ContextConfiguration(classes={RealMapTestConfiguration.class, ProdConfiguration.class})
@ActiveProfiles("bug") // spring.main.allow-bean-definition-overriding=true
public class MapImageServiceIT {
  @Autowired
  private GameMap map;
} 

It turns out that the injected GameMap into my test is a mock instance from TestConfiguration instead of the real thing from RealMapTestConfiguration. Aparrently in my test I have the configuration from ProdConfiguration and TestConfiguration, when I wanted ProdConfiguration and RealMapTestConfiguration. As the beans defined in the ProdConfiguration and *TestConfiguration are different the combination works, but TestConfiguration and RealMapTestConfiguration define the same been. It seems like the TestConfiguration is picked up by component scanning as it is in the same package as ProdConfiguration. I was under the impression that when overriding beans the bean definition that is closer to the test class would be preferred. However this seems not to be the case.

So here are my questions:

  1. When overriding beans, what is the order? Which bean overrides which one?
  2. How to go about to get the correct instance in my test (using a different bean name is not an option, as in reality the injected bean is not directly used in the test but in a service the test uses and there is no qualifier on it.)
like image 769
hotzst Avatar asked Sep 18 '25 21:09

hotzst


2 Answers

I've not used the spring.main.allow-bean-definition-overriding=true property, but specifying specific config in a test class has worked fine for me as a way of switching between objects in different tests.

You say...

It turns out that the injected GameMap into my test is a mock instance from TestConfiguration instead of the real thing from RealMapTestConfiguration.

But RealMapTestConfiguration does return a mock

package com.stackoverflow.impl;

@Configuration
public class RealMapTestConfiguration {
  @Bean
  public GameMap gameMap() {
    return Mockito.mock(GameMap.class);
  }
}
like image 146
pcoates Avatar answered Sep 21 '25 11:09

pcoates


I think the problem here is that including ContextConfiguration nullifies (part of) the effect of @SpringBootTest. @SpringBootTest has the effect of looking for @SpringBootConfiguration in your application (starting from the same package, I believe). However, if ContextConfiguration is applied, then configurations are loaded from there.

Another way of saying that: because you have ContextConfiguration in your test, scanning for @Configuration classes is disabled, and TestConfiguration is not loaded.

I don't think I have a full picture of your configuration setup so can't really recommend a best practice here, but a quick way to fix this is to add TestConfiguration to your ContextConfiguration in your test. Make sure you add it last, so that it overrides the bean definitions in the other two configurations.

The other thing that might work is removing @ContextConfiguration entirely and letting the SpringBootApplication scanning do its thing - that's where what you said about the bean definition that is closest may apply.

like image 37
akhaku Avatar answered Sep 21 '25 12:09

akhaku