Overriding Springboot Beans in integration test - spring-boot

I know there are a lot of examples out there about overriding beans in integration tests, but I'm getting a bit confused with Springboot versions, and I don't know what am I doing wrong.
I'm basically running a #SpringBootTest with #ActiveProfiles("test") and RestAssured. All annotations are in a base class I called BaseIntegrationTest, which looks like the following:
#ActiveProfiles("test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(
initializers = [BaseIntegrationTest.Companion.Initializer::class],
classes = [BaseIntegrationTest.Companion.ClockConfiguration::class]
)
abstract class BaseIntegrationTest {
#LocalServerPort
var localPort: Int = 0
#BeforeEach
fun beforeEachTest() {
RestAssured.port = localPort
}
companion object {
#TestConfiguration
class ClockConfiguration {
#Bean
fun mockClock(): Clock {
return Clock.fixed(
LocalDate
.of(2022, 1, 1)
.atTime(0, 0)
.toInstant(ZoneOffset.UTC),
ZoneId.of("UTC")
)
}
#Bean
fun mockCountryService(): CountryService {
return MockCountryService()
}
}
class MockCountryService : CountryService() {
override fun findByCode(countryCode: String): Country? {
return null
}
}
}
}
As you can see, I have a nested class with #TestConfiguration annotation (as I read it automatically recogizes it). By the way, I also included this CountryService bean because I was initially trying to mock the Clock, without success. I used this service to make sure that it was not a clock issue, but a bean overriding issue.
But when I run the tests, and put breakpoints along the production code, I see that what gets instantiated are the real beans, not these ones. I also tried to use #Configuration instead of #TestConfiguration, and adding #Primary to the beans here, but no luck.
If I remove the original beans, and I leave only these ones, then when debugging the production code I actually get the mocked beans, so I guess it has something to do in overriding them.
Springboot version is 2.7.2
Can anyone help me with that?
Thanks!

Related

Is there a way to overide automatically injected beans in Spring boot when writing tests?

I have a class annotated with a spring bean #Repository("clientDatasource") called ClientServiceDatasource which implements an interface called Datasource. I also have a mock implementation of this interface also annotated with a spring bean #Repository("mockDatasource") called MockClientServiceDatasource. I also have a class annotated with the spring bean #Service called ClientService and in in its constructor, I pass in a datasource. I do it like so:
#Service
class ClientService (#Qualifier("clientDatasource") private val dataSource: Datasource){}
As you can see that the service will default to the clientDatasource, because of the #Qualifier when the application is running.
However when I run my tests I annotate my test class with #SpringTest . In my understanding this means that it boots up the entire application as if it were normal. So I want to somehow overide that #Qualifier bean thats being used in the client service in my test so that the Client Service would then use the mockedDatasource class.
I'm fairly new to kotlin and spring. So I looked around and found ways to write a testConfig class to configure beans like so :
#TestConfiguration
class TestConfig {
#Bean
#Qualifier("clientDatasource")
fun mockDatasource(): Datasource {
return MockClientServiceDatasource()
}
}
and then using it in the test like so:
#SpringTest
#Import(TestConfig::class)
class ClientServiceTest {
...
}
I also asked chatGPT and it gave me this:
#SpringBootTest
class ClientServiceTest {
#Autowired
lateinit var context: ApplicationContext
#Test
fun testWithMockedDatasource() {
// Override the clientDatasource bean definition with the mockDatasource bean
val mockDatasource = context.getBean("mockDatasource", Datasource::class.java)
val mockClientDatasourceDefinition = BeanDefinitionBuilder.genericBeanDefinition(MockClientServiceDatasource::class.java)
.addConstructorArgValue(mockDatasource)
.beanDefinition
context.registerBeanDefinition("clientDatasource", mockClientDatasourceDefinition)
// Now the ClientService should use the mockDatasource when it's constructed
val clientService = context.getBean(ClientService::class.java)
// ... do assertions and other test logic here ...
}
}
But some of the methods don't work, I guess chatGPT knowledge is outdated.
I also looked through spring docs, but couldn't find anything useful.
Okay, So I took a look at the code previously with the TestConfig class. And I realised by adding the:
#Primary
annotation to the method inside my TestConfig class, it basically forces that to be the primary repository bean. Like so:
#TestConfiguration
class TestConfiguration {
#Bean
#Primary
#Qualifier("clientDatasource")
fun mockDatasource(): Datasource {
return MockClientDataSource()
}
}
and in the test I only imported the test and it just worked. I didn't have to autowire anything
This is my test class:
#SpringBootTest
#AutoConfigureMockMvc
#Import(TestConfiguration::class)
internal class ServiceControllerTest{
#Suppress("SpringJavaInjectionPointsAutowiringInspection")
#Autowired
lateinit var mockMvc: MockMvc
#Test
fun `should return all clients` () {
// when/then
mockMvc.get("/clients")
.andDo { print() }
.andExpect {
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON) }
jsonPath("$[0].first_name") {value("John")}
}
}
}

"No converter found for return value" when running Spring Boot MockMvc test

I'm trying to create a MockMvc test of a Spring Boot controller. I specifically do not want the entire application context to be spun up, so I am restricting the context to the controller in question. However, the test fails with a 500 with the following log output:
2020-03-03 13:04:06.904 WARN 8207 --- [ main] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class main.endpoints.ResponseDto]
It appears that the Spring Boot context does not know how to find Jackson.
Here is the controller
#RestController
class MyController {
#GetMapping("/endpoint")
fun endpoint(): ResponseDto {
return ResponseDto(data = "Some data")
}
}
data class ResponseDto(val data: String)
The test is as follows:
#SpringBootTest(
classes = [MyController::class],
webEnvironment = SpringBootTest.WebEnvironment.MOCK
)
#AutoConfigureMockMvc
internal class MyControllerTest(#Autowired private val mockMvc: MockMvc) {
#Test
fun `should work`() {
mockMvc.perform(MockMvcRequestBuilders.get("/endpoint").accept(MediaType.APPLICATION_JSON))
.andExpect(
content().json(
"""
{
"data": "Some data"
}
"""
)
)
}
}
The build.gradle file includes the following dependencies:
def jacksonVersion = "2.10.2"
testImplementation("com.fasterxml.jackson.core:jackson-core:2.10.2")
testImplementation("com.fasterxml.jackson.core:jackson-databind:2.10.2")
testImplementation("com.fasterxml.jackson.core:jackson-annotations:2.10.2")
Any ideas on how to get this to work?
The solution is to annotate the class with #WebMvcTest rather than #SpringBootTest. This configures enough context that the test can interact via MockMvc with the controller.
Unfortunately, enabling #WebMvcTest has another side effect: all beans specified by #Bean-annotated methods in the configuration are also instantiated. This is a problem when those methods cannot be executed in a test environment (e.g. because they access certain environment variables).
To solve this, I added the annotation #ActiveProfiles("test") to the test and #Profile("!test") to each such annotated method. This suppresses the invocation of those methods and the test works.
I'm not sure, but I think you need to specify what format the output will be. So something like
#GetMapping(value = ["/endpoint"], produces = [MediaType.APPLICATION_JSON])
So that spring knows to convert it to json and not say XML or something.
#EnableWebMvc might solve your issue.
According to Java Doc:
Adding this annotation to an #Configuration class imports the Spring MVC configuration from WebMvcConfigurationSupport,
e.g.:
#Configuration
#EnableWebMvc
#ComponentScan(basePackageClasses = MyConfiguration.class)
public class MyConfiguration {
}
To customize the imported configuration, implement the interface WebMvcConfigurer and override individual methods, e.g.:
#Configuration
#EnableWebMvc
#ComponentScan(basePackageClasses = MyConfiguration.class)
public class MyConfiguration implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry formatterRegistry) {
formatterRegistry.addConverter(new MyConverter());
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MyHttpMessageConverter());
}
}

How to write the unit test case for the Classes which are annotated with #Configuration in Spring Boot application

I have a Configuration Class, which creates the bean for RedissonClient and also Creates the CacheManager. How to create the Unit Test case for this Configuration classes.
Can we write unit test case for #Configuration Class?
If we can, How we need to develop.
I prefer to write the test case in Spock Framework, with Groovy. If not, using Junit or Mockito Framework. How to write the unit test case for the Classes which are annotated with #Configuration in Spring Boot application
#Configuration
public class CacheConfiguration {
private static final String CONFIG= "Configuration";
#Value("${redis.server.url}")
private String redisUrl;
#Value("${redis.server.password}")
private String password;
#Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress(redisUrl).setPassword(password);
RedissonClient client = Redisson.create(config);
return client;
}
#Bean
public CacheManager redissonCacheManager(RedissonClient redissonClient) {
Map<String, CacheConfig> config = new HashMap<String, CacheConfig>();
config.put(CONFIG, new CacheConfig(24*60*1000, 12*60*1000));
return new RedissonSpringCacheManager(redissonClient, config);
}
}
I think you should realize that classes annotated with #Configuration are not really java classes, or at least you should not treat them like this. I know it sound controversial, I'll explain...
So historically spring used XML configurations to declare beans.
In spring 2.5 I guess, they've introduced an annotation based method where you put annotations #Component/#Service on classes, put #Autowired wherever you want spring to inject dependencies, then spring starts, scans the classpath, detects the beans and starts the application context with these beans.
Then Spring 3.0 has introduced a Java Configuration way of doing spring related configurations:#Configuration / #Bean in a special class.
So you should view these configuration classes as a "substitution" to the methods I've described before
Now let me ask, do you think you should test and XML bean configuration on its own? Probably not...
Do you think you should test that class has annotation #Component on it and all the necessary dependencies are autowired (with reflection or whatever)? Probably not.
So why you want to test the Java Config classes?
Here is another argument that you might find interesting
I've said that these classes are solely for spring to resolve the beans and it internally runs them. But spring doesn't just "run" them - it creates run-time wrapper for them to overcome some technicalities. Here is an example of one such thing:
Lest assume we have three beans: A,B,C such as B and C depend on A.
class A {}
class B {
private A a;
public B(A a) {this.a = a;}
}
class C {
private A a;
public C(A a) {this.a = a;}
}
All beans are expected to be singletons so we define the configuration like this:
#Configuration
public class MyConfig {
#Bean
public A a() { return new A(); }
#Bean
public B b() { return new B(a()); }
public C c() {return new C(a()); }
}
Note that we call a() in definitions of B and C
Now, let me ask you a question: If its a "regular" java code how two different invocations of method a() (in B's and C's constructor) respectively are supposed to return the same instance of A?
So spring indeed uses a lot of sophisticated code and this is what actually runs in runtime, not your class as is, but the transformed version of it and you never know what are those transformations exactly since its and internal spring thing. But is so what is the point of testing it as it is?
I believe there are more arguments like this, but the point is clear - don't test the Configuration classes on their own.
Instead you can use an integration test that will run the spring container and load all the classes required in the configuration. However, in this case you'll probably want to mock some classes (by using #MockBean for example) so it won't be a 100% accurate test.
In terms of code coverage - IMO you should exclude these classes from coverage altogether
After some research and found that, we can run the embedded redis server and we can check whether we able to connect to redis server by spin up the application. I don't if this is correct or not. But by doing so, it really takes time to complete it, took around 20 seconds. Used following dependency to test this // https://mvnrepository.com/artifact/it.ozimov/embedded-redis
testCompile group: 'it.ozimov', name: 'embedded-redis', version: '0.7.2'
#SpringBootTest(classes = [TestApp])
class CacheConfigurationSpec extends Specification {
#Shared
RedisServer redisServer;
def setupSpec() {
redisServer = RedisServer.builder()
.port(6379)
.setting("bind 127.0.0.1")
.setting("maxmemory 128M")
.build()
redisServer.start()
}
def cleanupSpec() {
if (redisServer != null) {
redisServer.stop()
}
}
#Autowired
private RedissonClient redissonClient;
def "load all contexts"() {
}
}
#SpringBootApplication
class TestApp {
static void main(String[] args){
SpringApplication.run(TestApp.class, args)
}
}

Mocking a #Bean defined in a #ConfigurationProperties Class

I have a #ConfigurationProperties class like this:
#ConfigurationProperties(prefix = "myprops", ignoreUnknownFields = false)
#Configuration
public class MyProperties {
private Long mySchedulerRate;
#Bean
public Long mySchedulerRate() {
return this.mySchedulerRate;
}
}
I'm registering it as a bean so I can refer to it in an annotation for a Spring scheduler:
#Scheduled(fixedRateString = "#{#mySchedulerRate}")
public void runScheduledUpdate() {
...
{
However, I now want to write a unit test where I want to be able to set a different value for the bean 'mySchedulerRate'. Mocking/Spying on the #ConfigurationProperties class doesnt seem to work since the scheduler gets set up before the stubbing has been set to return my desired value.
What is the easiest way to achieve what I am trying to do?
Managed to fix this now. I was running a #SpringBootTest and I realise you can override properties here within the annotation for a particular test class.
This worked for me:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyApp.class, properties = "myprops.my-scheduler-rate=1000")
public class MyTest {
So no need to try and override the bean, I was overcomplicating this far too much.

Spring Integration Tests - idiomatic way of overriding beans

how can I override beans in Spring (Boot) Integration Tests the idiomatic way?
Up until now I had source configuration like this:
#Configuration
class ApplicationConfiguration {
#Bean
CarsRepository carsRepository() {
// return some real sql db
}
}
And tests like this:
#SpringBootTest
class ApplicationISpec extends Specification {
#Configuration
#Import(Application.class)
static class TestConfig {
#Bean
#Primary
CarsRepository testsCarsRepository() {
// return some in-memory repository
}
}
def "it can do stuff with cars"() {
// do some REST requests to application and verify it works
// there is no need to make real calls to real DB
}
}
First thing is that test bean testsCarsRepository method must differ than original one (which is not obvious, and there is no warning/error about it).
But the final question is: what is the idiomatic way of overriding beans with Spring in integration tests?
When I posted my WTF about method name on Twitter - Stephane Nicoll said the #Primary is not intended to be used for overriding beans in tests.
So what is the preferred way of that?
You can use #Profile together with #ActiveProfile annotation to separate you test and production configurations. For example change you test config to:
#SpringBootTest
#ActiveProfiles("test")
class CarsISpec extends Specification {
#Configuration
#Import(Application.class)
#Profile("test")
static class TestConfig {
#Bean
CarsRepository testsCarsRepository() {
// return some in-memory repository
}
}
}
Don't forget to mark you production configuration ApplicationConfiguration with #Profile("!test").
Also Spring Boot provides numerous tools for testing (e.g. #DataJpaTest with embedded database, #MockBean for mocking beans in context and etc.) Link to doc

Resources