Kotlin tests with repository ( spring-boot, kotlin, jersey, jax-rs ) - spring-boot

I have working kotlin service, but when i try to write test for it i get stuck because i cant get all my services initialized no matter what...
#RunWith(SpringRunner::class)
class DataServiceTest {
#InjectMocks
private lateinit var DataService : DataService
#Mock
private lateinit var updateDataService: UpdateDataService
#Test
fun shouldUpdateCustomerEmail() {
DataService.setNewCustomerEmail("221722", ApiEmail("test#test.org"))
}
}
which calls DataService class:
#Autowired
private lateinit var updateDataService: UpdateDataService
.....
fun setNewCustomerEmail(id: String, email: ApiEmail) {
updateDataService.setNewCustomerEmail(id, email)
}
which calls UpdateDataService class:
#Service
open class UpdateDataService {
#Autowired
private lateinit var addressRepository: AddressRepository
fun setNewCustomerEmail(id: String, email: ApiEmail) {
val AddressList = getCustomerAddressList(id)
mapNewEmailToAddressList(AddressList, email)
cusAddressRepository.saveAll(AddressList)
}
fun getCustomerCusbaAddressList(id: String) : List<Address> {
return addressRepository.findAddressByCustomerId( id )
}
fun mapNewEmailToAddressList(cusbaAddressList : List<Address>, email: ApiEmail) {
AddressList.map { DataUtil.trimAddressFields( it ) }
AddressList.map { it.email = email.email }
}
}
I've tried lots of different #RunWith() properties and lots of different ways to Autowire / Mock / InjectMocks but to no avail.
Problem:
At the moment with this code. AddressRepository will be uninitialized when test gets to UpdateDataService.class in line. return addressRepository.findAddressByCustomerId( id )
Question:
How do i wire all these services in such way that services are loaded when app is running, but also test would know how to wire these services and repository ?

Using Spring Boot Test you can operate context easily
#RunWith(SpringRunner::class)
#SpringBootTest
class DataServiceTest
or provide a #TestConfiguration

Related

Unit Testing on Springboot WebClient with MockWebServer

After searching around the net for few days, couldn't really find the resources I need for my use case as many of it are implemented in Java.
I have a service class that is calling external api, and I am trying to write unit tests on it with MockWebServer
#Service
class ApiService {
#Autowired
private lateinit var webClient: WebClient
fun getApiResult(name: String): ResultDto? {
return try {
webClient
.get()
.uri("https://www.example.com/")
.retrieve()
.bodyToMono(Result::class)
.block()
catch {
null
}
}
}
#ExtendWith(MockitoExtension::class)
#MockitoSettings(strictname = Strictness.LENIENT)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ApiServiceTest {
#Mock
private lateinit var webClient: WebClient
#InjectMocks
private lateinit var mockApiService: ApiService
private val mockName = "mockName"
private val mockDetailDto = DetailDto(name = "mockName", age = 18)
#Test
fun canGetDetails() {
mockWebServer.enqueue(
MockResponse()
.setResponse(200)
.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.setBody(mockDetailDto))
)
mockApiService.getApiResult(mockName)
val request: RecordedRequest = mockWebServer.takeRequest()
// Assert statements with request
}
}
However, I am getting lateinit property webClient has not been initialised. But if I were to use #Spy instead of #Mock for the webClient in my test, it will be making actual apis calls which is not the goal of unit test. My goal would be able to check the request for some of the details such as GET and its return value. Appreciate the community's help.
By my opinion you not add some configuration, for resolve it automatically you can try make Integration Test for this. Like this
#WithMockUser//if you use spring secutity
#AutoConfigureMockMvc
#SpringBootTest(webEnvironment = RANDOM_PORT)
class MyTest {
#Autowired
private MockMvc mvc;
}
and make a test application for quickly loading environment
#SpringBootApplication
#ConfigurationPropertiesScan
#ComponentScan(lazyInit = true)//make loading is faster
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
This way can load full context configuration like prod mode, of course only for called methods if you used lazyInit = true

kotlin.UninitializedPropertyAccessException: lateinit property has not been initialized

This is my main function
object Service {
fun getConfigMappings(client: RedissonClient, request: GetRequest): IndataType {
****
return obj
}
}
I calling it in my main class, and everything works good, I can get the response.
#Autowired
lateinit var client: RedissonClient
val indataObj = Service.getConfigMappings(client, request)
When I want to write a test for it, I got error "kotlin.UninitializedPropertyAccessException: lateinit property client has not been initialized", can anyone help me with that?
"
class ServiceTest {
#Autowired
lateinit var client: RedissonClient
#Test
fun `test1`() {
val request = GetRequest {
***
}
val indataObj = Service.getConfigMappings(client, request)
}
}
kotlin.UninitializedPropertyAccessException: lateinit property has not been initialized is occured because there is no configuration for AutoWired.
If you want to use AutoWired in the unit test, it needs annotation for configuration.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations="classpath:config/springbeans.xml")
public class BeanSpringTest {
if you want to use #Autowired, there must have a #Bean somewhere

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.

SpringBoot #Autowire not working inside #Repository

I have a multi module SpringBoot app with gradle and kotlin, trying spring-data-r2dbc. If i use #Repository annotation over my Repository class, #Autowired DatabaseClient is null. But if i change annotation to #Component, #Autowired works and I do successfull call to database. Any idea why #Autowire isn't working with #Repository annotation?
Database configuration class:
#Configuration
open class DatabaseConfiguration(
#Value("\${spring.data.mssql.host}") private val host: String,
// #Value("\${spring.data.mssql.port}") private val port: Int,
#Value("\${spring.data.mssql.database}") private val database: String,
#Value("\${spring.data.mssql.username}") private val username: String,
#Value("\${spring.data.mssql.password}") private val password: String)
: AbstractR2dbcConfiguration() {
#Bean
override fun connectionFactory(): ConnectionFactory {
return MssqlConnectionFactory(
MssqlConnectionConfiguration.builder()
.host(host)
//.port(port)
.database(database)
.username(username)
.password(password).build()
)
}
}
Main class:
#SpringBootApplication
#EnableR2dbcRepositories
class MultigradleApplication
Repository class (in module "data"):
#Repository
open class TestRepo() {
#Autowired
lateinit var client: DatabaseClient
fun getAll() : Flux<PersonDTO> {
return client.execute("SELECT * FROM Person.Person")
.`as`(PersonDTO::class.java)
.fetch()
.all()
}
}
The problem was that I injected PersonService as its IPersonService interface into my Controller, but I didn't have an interface for TestRepo and injected it directly. When I added ITestRepo interface to TestRepo and injected it into the service as ITestRepo, everything started working.

How to use spring annotations like #Autowired in kotlin?

Is it possible to do something like following in Kotlin?
#Autowired
internal var mongoTemplate: MongoTemplate
#Autowired
internal var solrClient: SolrClient
Recommended approach to do Dependency Injection in Spring is constructor injection:
#Component
class YourBean(
private val mongoTemplate: MongoTemplate,
private val solrClient: SolrClient
) {
// code
}
Prior to Spring 4.3 constructor should be explicitly annotated with Autowired:
#Component
class YourBean #Autowired constructor(
private val mongoTemplate: MongoTemplate,
private val solrClient: SolrClient
) {
// code
}
In rare cases, you might like to use field injection, and you can do it with the help of lateinit:
#Component
class YourBean {
#Autowired
private lateinit var mongoTemplate: MongoTemplate
#Autowired
private lateinit var solrClient: SolrClient
}
Constructor injection checks all dependencies at bean creation time and all injected fields is val, at other hand lateinit injected fields can be only var, and have little runtime overhead. And to test class with constructor, you don't need reflection.
Links:
Documentation on lateinit
Documentation on constructors
Developing Spring Boot applications with Kotlin
Yes, java annotations are supported in Kotlin mostly as in Java.
One gotcha is annotations on the primary constructor requires the explicit 'constructor' keyword:
From https://kotlinlang.org/docs/reference/annotations.html
If you need to annotate the primary constructor of a class, you need to add the constructor keyword to the constructor declaration, and add the annotations before it:
class Foo #Inject constructor(dependency: MyDependency) {
// ...
}
You can also autowire dependencies through the constructor. Remember to annotate your dependencies with #Configuration, #Component, #Service etc
import org.springframework.stereotype.Component
#Component
class Foo (private val dependency: MyDependency) {
//...
}
like that
#Component class Girl( #Autowired var outfit: Outfit)
If you want property injection but don't like lateinit var, here is my solution using property delegate:
private lateinit var ctx: ApplicationContext
#Component
private class CtxVarConfigurer : ApplicationContextAware {
override fun setApplicationContext(context: ApplicationContext) {
ctx = context
}
}
inline fun <reified T : Any> autowired(name: String? = null) = Autowired(T::class.java, name)
class Autowired<T : Any>(private val javaType: Class<T>, private val name: String?) {
private val value by lazy {
if (name == null) {
ctx.getBean(javaType)
} else {
ctx.getBean(name, javaType)
}
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
}
Then you can use the much better by delegate syntax:
#Service
class MyService {
private val serviceToBeInjected: ServiceA by autowired()
private val ambiguousBean: AmbiguousService by autowired("qualifier")
}

Resources