Autowire Spring TestEntityManager in Kotlintest not working - spring-boot

I am, as a learning experience, trying to port the tests described in Spring Boot's kotlin tutorial (https://spring.io/guides/tutorials/spring-boot-kotlin/) from using JUnit 5 to KotlinTest.
In a repositories test annotated with #DataJpaTest, I manage to autowire my JPA repositories fine. But autowiring Spring Boot's TestEntityManager does not work, as I get java.lang.IllegalStateException: No transactional EntityManager found when I try to use it in a test.
In addition to #DataJpaTest, I tried adding #Transactional, without luck.
When running the test with JUnit5, persisting an entity with the entityManager works fine.
#DataJpaTest
class RepositoriesTests #Autowired constructor(
val entityManager: TestEntityManager,
val userRepository: UserRepository,
val articleRepository: ArticleRepository) {
#Test
fun `When findByIdOrNull then return article`() {
val juergen = User(login = "springjuergen", firstname = "Juergen", lastname = "Hoeller")
entityManager.persist(juergen) <--- OK
But with KotlinTest, it fails:
#DataJpaTest
class RepositoriesTests #Autowired constructor(
val entityManager: TestEntityManager,
val userRepository: UserRepository,
val articleRepository: ArticleRepository) : StringSpec() {
init {
"When findByIdOrNull then return article" {
val juergen = User(login = "springjuergen", firstname = "Juergen", lastname = "Hoeller")
entityManager.persist(juergen) <--- FAIL
The error being java.lang.IllegalStateException: No transactional EntityManager found.
I haven't been able to figure out how to provide this entitymanager in the test implemented using KotlinTest.

Just create transaction wrapper, like this:
#Component
class TransactionWrapperUtils {
#Transactional
public fun runWithTransaction(f: () -> Unit) {
f()
}
}
And call it
transactionWrapperUtils.runWithTransaction { entityManager.persist(juergen) }

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")}
}
}
}

spring boot startup process failed with lateinit var not initialized : DI Issue

I'm buidling a backend that will fetch some data from several externals APIs, to populate DB after some processes, and then expose these data via a rest api. Using JPA and postgresql, in springboot.
I have created the entities, repositories and fetch external apis from a webclient.
However, I have a dependency injection issue. I read tons of articles and issues, but can not make it work. When trying to inject the repository, I got the well known error lateinit var not initialized.
I also tried constructor injection, but still doesn't work. It seems that the repository is not considered to be a bean that could be autowired.
Any help would be appreciated
FIXED SEE SOLUTION BELOW. PB WAS IN THE SERVICE
Application. Running the startuprocess from the context
#SpringBootApplication(exclude = [JacksonAutoConfiguration::class])
class SpringbootkotlinApplication
fun main(args: Array<String>) {
val context = runApplication<SpringbootkotlinApplication>(*args)
val startupProcess = context.getBean(StartupProcesses::class.java)
startupProcess.fetchGroup()
}
startup class as #component :fetches external api and calls a service to save data in db
#Component
class StartupProcesses {
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource() //<-- fetch ext api OK
SplitDataSourceToEntities().populateGroup(fetch) //<-- call service to populate db
}
}
Service that should populates the DB through repository but error in lateinit repo
#Service
class SplitDataSourceToEntities {
#Autowired
lateinit var repo:IronMaidenGroupRepository // <-- error is here
fun populateGroup(dto: DTOIronMaidenAPi): IronMaidenGroupEntity {
val dbgroup = IronMaidenGroupEntity(
groupId = dto.id!!,
name = dto.name ?: ""
)
return repo.save(dbgroup)
}
}
the repo, extending JPA repository
import com.jerome.springbootkotlin.model.dbentities.ironmaidengroupentity.IronMaidenGroupEntity
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
#Repository
interface IronMaidenGroupRepository:JpaRepository<IronMaidenGroupEntity, String> {
}
SOLUTION
Service should be defined like this (the SplitDataSourceToEntitiesclass should also be late init instead of beeing instantiated)
#Component
class StartupProcesses {
#Autowired
lateinit var splitDataSourceToEntities: SplitDataSourceToEntities // <--add this lateinit
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource()
splitDataSourceToEntities.populateGroup(fetch) //<-- fix is here
}
}
#Autowired works only when managed by Spring
It is important to note, that #Autowired does only work when the class has been initialized by Spring. In your code, you have an #Autowired annotation in class SplitDataSourceToEntities, but you manually instantiate SplitDataSourceToEntities in StartupProcesses.fetchGroup. That cannot work, because Spring had no possibility to auto-wire the lateinit var.
The problem can easily be solved by using an autowired instance of your service:
#Component
class StartupProcesses {
#Autowired
lateinit var splitDataSourceToEntities: SplitDataSourceToEntities
fun fetchGroup() {
val fetch = IronMaidenSourceApi.fetchIronMaidenSource() //<-- fetch ext api OK
splitDataSourceToEntities.populateGroup(fetch) //<-- call service to populate db
}
}
Help Spring finding your JpaRepository
Additionally, you probably need to add an #EnableJpaRepositories annotation to your application:
#EnableJpaRepositories(basePackageClasses = [IronMaidenGroupRepository::class])
#SpringBootApplication(exclude = [JacksonAutoConfiguration::class])
class SpringbootkotlinApplication
...
Instead of basePackageClasses = ... you can just define the package directly by name, but the package name is not included in your example.
Do you have to use #Autowired at all?
Considering your question in the comments:
There is nothing wrong with your design, because it is not not necessary to "play" with #Component and #Autowired. You could as well get rid of the #Autowired annotations and define those variables as, e.g., constructor parameters.
That could look like this:
class SpringbootkotlinApplication
fun main(args: Array<String>) {
val context = runApplication<SpringbootkotlinApplication>(*args)
val repo = context.getBean(IronMaidenGroupRepository::class.java)
StartupProcesses(SplitDataSourceToEntities(repo))
}
#Component
class StartupProcesses(
val splitDataSourceToEntities: SplitDataSourceToEntities
) {
...
}
#Service
class SplitDataSourceToEntities(
val repo: IronMaidenGroupRepository
) {
...
}
Now only your Repository is managed by Spring, but on the flip-side you have to manage everything else by yourself which might get very tedious when your classes and their dependencies grow. It is much more comfortable (and in the end leads to better readable code) to let just Spring manage all the dependencies.

Spring Boot Test with JPA Entities

I have a Spring Boot application (written in Kotlin) which exposes a REST endpoint through a Controller QueueController. I'm testing that controller in a SpringBootTest by calling that endpoint using MockMvc as follows (some boilerplate omitted for readability):
// QueueControllerIT.kt
#SpringBootTest
#AutoConfigureMockMvc
#EnableAutoConfiguration(
exclude = [
JpaRepositoriesAutoConfiguration::class,
DataSourceAutoConfiguration::class,
HibernateJpaAutoConfiguration::class
]
)
#DirtiesContext
internal class QueueControllerIT {
lateinit var mockmvc: MockMvc
#Autowired
lateinit var wac: WebApplicationContext
#BeforeEach
fun createStubs() {
mockmvc = MockMvcBuilders.webAppContextSetup(wac).build()
}
#Test
fun `calling my endpoint`() {
mockmvc.perform(
MockMvcRequestBuilders.post("/stores/1/zones/one2one/customers/last")
).andExpect(status().isNoContent)
}
}
The problem is that when calling that endpoint requests to a DB are made. The DB is modelled using JPA-Entities and CRUD-Repositories, e.g. as follows:
interface StoreRepository : CrudRepository<Store, Int> {
fun findByExternalId(externalId: String): Store?
}
#Entity
class Store {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int? = null
#Column(name = "external_id")
lateinit var externalId: String
// more columns and relationships to other tables here
}
I am currently mocking the repositories and entities using Mockk as follows:
// QueueControllerIT.kt
#MockkBean
lateinit var storeRepositoryMock: StoreRepository
#Test
fun `calling my endpoint`() {
val store = Store()
every { storeRepositoryMock.findById(storeId) } returns Optional.of(store)
mockmvc.perform(
MockMvcRequestBuilders.post("/stores/1/zones/one2one/customers/last")
).andExpect(status().isNoContent)
}
However, that produces the following error:
kotlin.UninitializedPropertyAccessException: lateinit property externalId has not been initialized
Of course I could now mock/stub all the lateinit properties of store until my test succeeds, but there must be an easier way. I read a bit about #DataJpaTest and I think it would actually be exactly what I need, because it performs read/writes real entities to an in-memory database. However just adding #DataJpaTest to my test does not seem to work well with the existing setup above.
:
java.lang.IllegalStateException: Configuration error: found multiple declarations of #BootstrapWith for test class [com.swisscom.oce.bouncer.controller.queue.QueueControllerIT]: [#org.springframework.test.context.BootstrapWith(value=org.springframework.boot.test.context.SpringBootTestContextBootstrapper), #org.springframework.test.context.BootstrapWith(value=org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper)]
What am I doing wrong? How can I test a Spring Boot application using #SpringBootTest so I have a real application context and at the same time I don't have to worry about mocking/stubbing away the whole persistence layer?
I believe that you can annotate the test class with #Transactional and the database data will be reverted after each test :)
So you won't have to mock anything repository-related.

how to approach Spring boot constructor based dependency injection confusion/frustration when unit testing a component class with several dependencies

I am trying to write unit tests for a Spring boot service using JUnit 4 and Mockito.
I used constructor based dependency injection for my service, The Signature is:
class VbServiceImp(val jdbcTemplate: NamedParameterJdbcTemplate,
val nuanceService: NuanceService,
val conf: AppConfigProps,
val eventService: EventServiceImp,
val audioTrimService: AudioTrimServiceIF,
val vbNuanceStagingDeletionsService: VbNuanceStagingDeletionsService) : VbService {...}
in another part of the application This service gets injected into a controller and somehow spring just magically knows what to inject without me specifying this (Any idea how this works/explanation would be appreciated, guess it's based on component scan?)
example:
class VbController(val vbService: VbService) {...}
Now in my VBServiceImpl Unit test class I try to mock all the above dependencies before declaring vBService in order to manually inject all dependencies into VBService during declaration.
the relevant part of my test class looks like this:
#RunWith(SpringRunner::class)
#ContextConfiguration()
class VBServiceTests {
#MockBean
val jdbcTemplate: NamedParameterJdbcTemplate = mock()
#MockBean
val nuanceService: NuanceService = mock()
#MockBean
val appconfigProps: AppConfigProps = AppConfigProps()
#MockBean
val eventService: EventServiceImp = mock()
#MockBean
val audioTrimService: AudioTrimService = mock()
#MockBean
val vbNuanceStagingDeletionsService: VbNuanceStagingDeletionsService = mock()
val vbService: VbServiceImp = VbServiceImp(jdbcTemplate, nuanceService, appconfigProps, eventService, audioTrimService, vbNuanceStagingDeletionsService)
#SpyBean
val vbServiceSpy: VbServiceImp = Mockito.spy(vbService)
#Before
fun setup() {
initMocks(this)
}
When I run a test I get the exception below. If I understand this correctly there is already a bean of type jdbcTemplate in the application context and therefore I can't define the #Mockbean jdbcTemplate above?
exception:
private final org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate com.cc.ff.vb.service.VBServiceTests.jdbcTemplate cannot have an existing value
So now the issue is: If I removed the #MockBean jdbcTemplate variable then I can't inject jdbcTemplate when I declare vbService in my test class. So how could I get around this/make this work?
Just to check I removed the jdbcTemplate parameter from the vbService class constructor and changed it to a #Autowired field injected class variable and provided the mock class using #TestConfig. This worked however then the exception popped up on the next constructor parameter (NuanceService)
i'm out of ideas and google hasn't returned anything of value. Do I remove all constructor injected dependencies and then make them field injected using #Autowired and then provide the beans in the nested #TestConfig annotated class or is there a better/cleaner way? AFAIK field based injection is supposed to be bad practice?
example of providing correct bean for testing #Autowired field injected jdbcTemplate variable:
#TestConfiguration
class testConfig {
#Bean
fun jdbcTemplate(): NamedParameterJdbcTemplate {
return mock<NamedParameterJdbcTemplate>()
}
}
This is how I ended up making it work:
#TestConfiguration
class testConfig {
#Bean
fun jdbcTemplate(): NamedParameterJdbcTemplate {
return mock<NamedParameterJdbcTemplate>()
}
#Bean
fun nuanceService(): NuanceService {
return mock<NuanceService>()
}
#Bean
fun appConfigProps(): AppConfigProps {
return mock<AppConfigProps>()
}
#Bean
fun eventService(): EventServiceImp {
return mock<EventServiceImp>()
}
#Bean
fun audioTrimService(): AudioTrimService {
return mock<AudioTrimService>()
}
#Bean
fun vbNuanceStagingDeletionService(): VbNuanceStagingDeletionsService {
return mock<VbNuanceStagingDeletionsService>()
}
}
#MockBean
lateinit var nuanceService: NuanceService
#SpyBean
lateinit var vbServiceSpy: VbServiceImp
I'm still not sure if this is the best/optimal way of going about this so would appreciate some more details...

Unable to test custom repositories using Spring Boot Test

I am using Spring Boot 1.4.4. Followed the Spring Boot Test article for unit tests. When I have custom repositories, test is not working and fails with error UnsatisfiedDependencyException: Error creating bean with name 'com.jay.UserRepositoryTest': Unsatisfied dependency expressed through field 'userRepository';
Here is my code,
#Repository
#Transactional
public class UserRepository {
#Autowired
private EntityManager entityManager;
// sample code for custom repo. can be done easily in CrudRepo
public User findUser(String name){
TypedQuery<User> q = entityManager.createQuery("Select u from User u Where u.name = :name", User.class);
q.setParameter("name", name);
return q.getSingleResult();
}
}
#RunWith(SpringRunner.class)
#DataJpaTest
public class UserRepositoryTest {
#Autowired
private TestEntityManager entityManager;
#Autowired
private UserRepository userRepository;
#Test
public void findUserTest(){
...
}
}
But I am able to test the following Dao with Spring Boot without any config change,
#Transactional
public interface UserDao extends CrudRepository<User, Long> {
User findByEmail(String email);
}
When I am using #SpringBootTest, I am able to inject UserRepository, but not TestEntityManager.
Posted the same question in Github of Spring Boot Test and got reply. Refer this

Resources