Spring boot with testcontainers and jOOQ doesn't inject DSL context - spring-boot

I have some problem with spring boot + jOOQ and testcontainers. DSL context won't inject in my test class.
I made some preparations for using SQLContainer
class SpringTestContainer: PostgreSQLContainer<SpringTestContainer> {
private val postgreSqlPort = 5432
private val db = "m4"
companion object {
var instance: SpringTestContainer? = null
fun get(): SpringTestContainer {
if(instance == null) {
instance = SpringTestContainer()
}
return instance!!
}
}
override fun getDatabaseName(): String = db
constructor() : this("registry.dev.tskad.stdev.ru/m4/db:latest")
constructor(dockerImageName: String) : super(dockerImageName){
withImagePullPolicy(PullPolicy.alwaysPull())
addExposedPort(postgreSqlPort)
waitStrategy = LogMessageWaitStrategy()
.withRegEx(".*database system is ready to accept connections.*\\s")
.withTimes(1)
.withStartupTimeout(Duration.of(30, ChronoUnit.SECONDS))
}
override fun getJdbcUrl(): String {
return String.format("jdbc:postgresql://%s:%d/%s", containerIpAddress, getMappedPort(postgreSqlPort), databaseName)
}
override fun waitUntilContainerStarted() {
getWaitStrategy().waitUntilReady(this)
}
override fun getLivenessCheckPorts(): Set<Int?> {
return HashSet(getMappedPort(postgreSqlPort))
}
}
Then I created some abstraction for extending my integration test classes
#ContextConfiguration(initializers = [SpringIntegrationTest.Initializer::class])
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#JooqTest
abstract class SpringIntegrationTest {
#get:Rule
var postgreSQLContainer = SpringTestContainer.get()
inner class Initializer: ApplicationContextInitializer<ConfigurableApplicationContext> {
override fun initialize(applicationContext: ConfigurableApplicationContext) {
with(applicationContext.environment.systemProperties) {
put("spring.datasource.url", postgreSQLContainer.jdbcUrl)
put("spring.datasource.username", postgreSQLContainer.username)
put("spring.datasource.password", postgreSQLContainer.password)
}
}
}
}
and then I implemented test class
#ExtendWith(SpringExtension::class)
class TransactionRepositoryImplTest: SpringIntegrationTest() {
#Autowired
private var dslContext: DSLContext? = null
private var transactionRepository: TransactionRepository? = null
#Before
fun setUp() {
assertThat(dslContext).isNotNull
transactionRepository = TransactionRepositoryImpl(dslContext!!)
}
#After
fun tearDown() {
}
#Test
fun findTransactionData() {
transactionRepository?.findTransactionByVehicleUuid(null).apply {
assertNull(this)
}
}
}
and when I started tests of this class - tests are fails, because of assertions are not passed.
Here is the report of tests https://pastebin.com/0HeqDcCT
So.. how it impossible? I saw a few guides with this stack(Spring/jOOQ/TestContainers). And they are all working. Maybe I missed some test dependencies? If you have experience with this case - share your solution, please. I will be very grateful.
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.springframework.boot:spring-boot-starter-jooq")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.cloud:spring-cloud-starter-consul-config")
implementation("org.springframework.cloud:spring-cloud-stream")
implementation("org.springframework.cloud:spring-cloud-stream-binder-kafka")
implementation("org.springframework.kafka:spring-kafka")
implementation("org.springframework.boot:spring-boot-starter-amqp")
implementation ("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.3")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.3")
runtimeOnly("org.postgresql:postgresql:42.2.12")
jooqGeneratorRuntime("org.postgresql:postgresql:42.2.12")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
exclude(module = "junit")
}
testImplementation("com.ninja-squad:springmockk:2.0.1")
testImplementation("org.springframework.cloud:spring-cloud-stream-test-support")
testImplementation("org.springframework.kafka:spring-kafka-test")
testImplementation("org.springframework.amqp:spring-rabbit-test")
testImplementation("org.testcontainers:postgresql:1.14.3")
}

I might be missing something but in that setup, you have to manually run
postgreSQLContainer.start() somewhere. As an example it could be done in #BeforeAll.

I found the solution. The right way was to override the start method of test containers implementation and put in system properties the credentials to container DB.
Here is the working code:
class SpringTestContainer: PostgreSQLContainer<SpringTestContainer> {
private val postgreSqlPort = 5432
private val db = "m4"
companion object {
var instance: SpringTestContainer? = null
fun get(): SpringTestContainer {
if(instance == null) {
instance = SpringTestContainer()
}
return instance!!
}
}
override fun getDatabaseName(): String = db
constructor() : this("registry.dev.tskad.stdev.ru/m4/db:latest")
constructor(dockerImageName: String) : super(dockerImageName){
withImagePullPolicy(PullPolicy.alwaysPull())
addExposedPort(postgreSqlPort)
waitStrategy = LogMessageWaitStrategy()
.withRegEx(".*database system is ready to accept connections.*\\s")
.withTimes(1)
.withStartupTimeout(Duration.of(30, ChronoUnit.SECONDS))
}
override fun getJdbcUrl(): String {
return String.format("jdbc:postgresql://%s:%d/%s", containerIpAddress, getMappedPort(postgreSqlPort), databaseName)
}
override fun waitUntilContainerStarted() {
getWaitStrategy().waitUntilReady(this)
}
override fun getLivenessCheckPorts(): Set<Int?> {
return HashSet(getMappedPort(postgreSqlPort))
}
override fun start() {
super.start()
val container = get()
System.setProperty("DB_URL", container.jdbcUrl)
System.setProperty("DB_USERNAME", container.username)
System.setProperty("DB_PASSWORD", container.password)
}
}
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#SpringBootTest(properties = ["spring.cloud.consul.enabled = false"])
#EnableAutoConfiguration(exclude = [
RabbitAutoConfiguration::class,
KafkaAutoConfiguration::class
])
class TransactionRepositoryImplTest {
#get:Rule
var postgreSQLContainer = SpringTestContainer.get()
#Autowired
private lateinit var dslContext: DSLContext
#Autowired
private lateinit var transactionRepository: TransactionRepository
#MockkBean
private lateinit var connectionFactory: ConnectionFactory // this is the mock for rabbit connection. U may ignore it.
#Test
fun contextLoads() {
Assertions.assertNotNull(dslContext)
Assertions.assertNotNull(transactionRepository)
}
}
and then need to fix application.yml in the tests directory
spring:
datasource:
platform: postgres
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
driverClassName: org.postgresql.Driver

in java it helps me:
on test put annotation. See JooqAutoConfiguration.class
#Import({ DataSourceAutoConfiguration.class,
TransactionAutoConfiguration.class, JooqAutoConfiguration.class})

Related

Spring websockets with protobuf integration test fails due to conversion exception

I implemented a simple websocket with spring and protobuf. I am trying to write an integration test, however I am stuck with a conversion exception.
Implementation:
#Controller
class SampleWebSocketController {
companion object {
private val logger: Logger = LoggerFactory.getLogger(SampleWebSocketController::class.java)
}
#MessageMapping("/send")
#SendTo("/topic/test")
fun test(dateString: DateString): DateString {
logger.info("received proto DateString: ${dateString.date}")
return DateString.newBuilder().setDate(LocalDateTime.now().toString()).build()
}
#Configuration
#EnableWebSocketMessageBroker
class WebSocketConfiguration : WebSocketMessageBrokerConfigurer {
override fun configureMessageBroker(registry: MessageBrokerRegistry) {
registry.enableSimpleBroker("/topic")
registry.setApplicationDestinationPrefixes("/app")
}
override fun registerStompEndpoints(registry: StompEndpointRegistry) {
registry.addEndpoint("/websocket").setAllowedOriginPatterns("*")
registry.addEndpoint("/websocket").setAllowedOriginPatterns("*").withSockJS()
}
}
I also added:
#Bean
fun protobufMessageConverter(): ProtobufMessageConverter = ProtobufMessageConverter()
to the #SpringBootApplication annotated class
Test:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class WebSocketIntegrationTests {
#LocalServerPort
private var port: Int = 0
#Autowired
private lateinit var protobufMessageConverter: ProtobufMessageConverter
private lateinit var webSocketStompClient: WebSocketStompClient
#BeforeEach
fun setUp() {
webSocketStompClient =
WebSocketStompClient(SockJsClient(listOf(WebSocketTransport(StandardWebSocketClient()))))
webSocketStompClient.messageConverter = protobufMessageConverter
}
#Test
fun test() {
val blockingQueue = ArrayBlockingQueue<DateString>(1)
val session =
webSocketStompClient.connect("ws://localhost:$port/websocket", TestHandler()).get(3, TimeUnit.SECONDS)
session.subscribe("/topic/test", TestFrameHandler(blockingQueue))
val dateString = DateString.newBuilder().setDate(LocalDateTime.now().toString()).build()
session.send("/app/send", dateString)
await()
.atMost(3, TimeUnit.SECONDS)
.untilAsserted { assertEquals(dateString, blockingQueue.poll()) }
}
class TestHandler : StompSessionHandlerAdapter() {
}
class TestFrameHandler(private val queue: ArrayBlockingQueue<DateString>) : StompFrameHandler {
override fun getPayloadType(headers: StompHeaders): Type {
return DateString::class.java
}
override fun handleFrame(headers: StompHeaders, payload: Any?) {
queue.add(payload as DateString)
}
}
}
Log/Stacktrace when executing test:
2022-12-05 15:25:19.220 ERROR 10001 --- [nboundChannel-7] .WebSocketAnnotationMethodMessageHandler : Unhandled exception from message handler method
org.springframework.messaging.converter.MessageConversionException: Cannot convert from [[B] to [com.poc.model.DateString] for GenericMessage [payload=byte[28], headers={simpMessageType=MESSAGE, stompCommand=SEND, nativeHeaders={destination=[/app/send], content-type=[application/x-protobuf;charset=UTF-8], content-length=[28]}, simpSessionAttributes={}, simpHeartbeat=[J#59ccdbc9, contentType=application/x-protobuf;charset=UTF-8, lookupDestination=/send, simpSessionId=3b569541683b4f2f9dc81b462ad3b708, simpDestination=/app/send}]
at org.springframework.messaging.handler.annotation.support.PayloadMethodArgumentResolver.resolveArgument(PayloadMethodArgumentResolver.java:145)
at org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:118)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:147)
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:115)
at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMatch(AbstractMethodMessageHandler.java:569)
at org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler.handleMatch(SimpAnnotationMethodMessageHandler.java:511)
at org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler.handleMatch(SimpAnnotationMethodMessageHandler.java:94)
at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessageInternal(AbstractMethodMessageHandler.java:524)
at org.springframework.messaging.handler.invocation.AbstractMethodMessageHandler.handleMessage(AbstractMethodMessageHandler.java:458)
at org.springframework.messaging.support.ExecutorSubscribableChannel$SendTask.run(ExecutorSubscribableChannel.java:144)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
Assertion condition defined as a lambda expression in com.poc.WebSocketIntegrationTests that uses com.poc.model.DateString, com.poc.model.DateStringjava.util.concurrent.ArrayBlockingQueue expected:<date: "2022-12-05T15:25:19.204652"
> but was:<null> within 3 seconds.
I am currently not sure where I should configure or implement the mapping. Also I am a bit confused about the timing of the error. Is the message already sent and rejected by the controller because spring cannot convert the received bytes to DateString?

How to initialize repository from service in Springboot Kotlin

#Service
public class AvailablePolicyService {
#Autowired
private var availablePolicyRepository : AvailablePolicyRepository = **AvailablePolicyRepository()**
fun saveAvailablePolicy(availablePolicy: AvailablePolicy): AvailablePolicy { return availablePolicyRepository.save(availablePolicy) }
fun getAllAvailablePolicy(): List<AvailablePolicy>{ return availablePolicyRepository.findAll() }
fun getAvailablePolicyByPolicyId(policyId: String?): AvailablePolicy? {
var availablePolicies: List<AvailablePolicy> = getAllAvailablePolicy()
for (availablePolicy in availablePolicies) {
if (availablePolicy.getPolicyId().equals(policyId)) {
return availablePolicy
}
}
return null
}
fun getAvailablePolicyByPolicyCategory(policyCategory: String?): ArrayList<AvailablePolicy> {
var availablePolicies: List<AvailablePolicy> = getAllAvailablePolicy()
var availablePolicyCategory = ArrayList<AvailablePolicy>()
for (availablePolicy in availablePolicies) {
if (availablePolicy.getPolicyCategory().equals(policyCategory)) {
availablePolicyCategory.add(availablePolicy)
}
}
return availablePolicyCategory
}
}
#Repository
interface AvailablePolicyRepository : MongoRepository<AvailablePolicy, String>
The bolded text shows where the error is showing and it reads "Interface AvailablePolicyRepository does not have constructors". How do I initialize repository from service?
How do I initialize repository from service?
That's the thing. You don't! Spring does it for you:
#Autowired
private lateinit var availablePolicyRepository: AvailablePolicyRepository
Field injection is rather obsolete and you should consider using contructor injection instead.
#Service
class AvailablePolicyService(private val availablePolicyRepository: AvailablePolicyRepository) {...}

How to mock repository so it doesn't instert or pull data from DB

So my issue is that in my SpringBoot REST application im testing my RestController. The problem is that i don't know how to mock the repository so it doesn't get or puts data into the DB. I'm using Kotlin and Mockk for mocking
Here is my Repository
#Repository
interface StandingOrderRepository: CrudRepository<StandingOrder, Int> {
fun findByNameAndVariableSymbol(name: String, variableSymbol: String): List<StandingOrder>
fun findByValidFromBetween(fromDate: String, toDate: String): List<StandingOrder>
fun findByValidFromAfter(fromDate: String) : List<StandingOrder>
}
And here is my Test
#SpringBootTest
#AutoConfigureMockMvc
internal class StandingOrderResourceTest {
#Autowired
lateinit var mockMvc: MockMvc
#Autowired
lateinit var objectMapper: ObjectMapper
private val standingOrderMapper = mockk<StandingOrderMapper>()
private val standingOrderRepository = mockk<StandingOrderRepository>()
private val standingOrderServiceImpl = mockk<StandingOrderServiceImpl>()
private val standingOrderResource = StandingOrderResource(standingOrderServiceImpl)
val baseUrl = "/api"
#Nested
#DisplayName("GetStandingOrders()")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetStandingOrders {
#Test
fun `should return all StandingOrders`() {
standingOrderResource.getStandingOrders()
mockMvc.get(baseUrl)
.andDo { print() }
.andExpect {
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON)}
}
//standingOrderResource.getStandingOrders() shouldBe listOf(standingOrderDto)
}
}
}
The problem is if i Make a API call or invoke the mocked repository it still gets actual data from DB
In your test code you should try to use method whenever() from org.mockito.kotlin for stubbing StandingOrderRepository's method call.
For example your code for stubbing will looks something like this
whenever(standingOrderRepository.findByNameAndVariableSymbol(any(),any())).thenReturn(listOf(StandingOrder(...)))
UPD: So you use Mockk, then you shuold use method every instead whenever from mockito.
So this is how i made it work maybe the issue was on my side how i was trying to use it #Anton Tokmakov was correct here is how i did it
#SpringBootTest
#AutoConfigureMockMvc
#ExtendWith(SpringExtension::class)
internal class StandingOrderResourceTest #Autowired constructor(
val mockMvc: MockMvc,
val objectMapper: ObjectMapper,
) {
#MockkBean
private lateinit var standingOrderResource: StandingOrderResource
#Nested
#DisplayName("GetStandingOrders()")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetStandingOrders {
#Test
fun `should return all StandingOrders`() {
every { standingOrderResource.getStandingOrders() } returns
listOf(standingOrderDto1, standingOrderDto2)
mockMvc.get(baseUrl)
.andDo { print() }
.andExpect {
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON)}
}
.andExpect {
jsonPath("\$..[0]", match(MockMvcResultMatchers.content().json(Gson().toJson(
listOf(
standingOrderDto1,
standingOrderDto2
)), false)))
}
}
}

NullPointer with Reactive Spring Elastic Repository mocked into a service

This is the snippet of my test class:
#SpringBootTest
#ExtendWith(MockitoExtension::class)
class ResourceSearchServiceTest {
#Mock
lateinit var resourceSearchRepository: ResourceSearchRepository
#InjectMocks
lateinit var resourceSearchService: ResourceSearchService
lateinit var expectedResourceMetadata : ResourceSearchMetadata
#BeforeEach
fun createResourceMetadata() {
expectedResourceMetadata =
ResourceSearchMetadata(
id = UUID.randomUUID(),
pk = UUID.randomUUID(),
metadata = ResourceMetadata(
description = "blabla"
)
)
}
#Test
fun activeResourceMetadataTest() {
Mockito.`when`(resourceSearchRepository.findById(any(UUID::class.java))).thenReturn(Mono.just(expectedResourceMetadata))
Mockito.`when`(resourceSearchRepository.save(any(ResourceSearchMetadata::class.java))).thenReturn(Mono.just(expectedResourceMetadata))
resourceSearchService.updateActiveStatus(expectedResourceMetadata.id, false)
Mockito.verify(resourceSearchRepository, Mockito.times(1)).save(expectedResourceMetadata)
assertThat(expectedResourceMetadata.metadata.active).isFalse
}
}
and this is my service :
#Service
class ResourceSearchService(private val resourceSearchRepository: ResourceSearchRepository) {
fun updateActiveStatus(id: UUID, isActive: Boolean) {
resourceSearchRepository.findById(id).flatMap { resource ->
resource.metadata.active = isActive
resourceSearchRepository.save(resource)
}.map { Success }.defaultIfEmpty(NotFound)
}
}
when updateAciveStatus service method is called from the test class a nullpointer exception is raised by the repository at this line : resourceSearchRepository.findById(id)
It's like the mocked repository is not injected into the service.
Any idea what's wrong in my Kotlin code ?

Dagger2 ViewModel cannot be provided problem with #Binds ViewModelProvider factory

I have implemented the support for injection of ViewModels in complaience with
medium article
and googleRepo
unfortunately i get the error cannot be provided without an #Provides-annotated method. and i cant get to the bottom of it.
I have migrated the project to AndroidX. I have tried many answers available on StackOverflow without success.
AppComponent
#Singleton
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
MainActivityModule::class,
LoginActivityModule::class,
DispatcherActivityModule::class]
)
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(androidApplication: BeeApp)
}
AppModule
#Module(includes = [ViewModelModule::class])
class AppModule {
...(i provide non-related other object here)
}
ViewModelModule
#Suppress("unused")
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(UserProfileViewModel::class)
abstract fun bindUserViewModel(userProfileViewModel: UserProfileViewModel): ViewModel
#Binds
abstract fun bindViewModelFactory(viewModelFactory: DaggerViewModelFactory): ViewModelProvider.Factory
}
DaggerViewModelFactory
#Singleton
#Suppress("UNCHECKED_CAST")
class DaggerViewModelFactory #Inject constructor(private val viewModelsMap: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = viewModelsMap[modelClass] ?: viewModelsMap.asIterable().firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
Fragment
class UserProfileFragment : BaseFragment(), Injectable {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var profileViewModel: UserProfileViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
profileViewModel = ViewModelProviders.of(this, viewModelFactory)
.get(UserProfileViewModel::class.java)
}
(...)
}
ViewModel
class UserProfileViewModel #Inject constructor(userAccountRepository: UserAccountRepository) : ViewModel() {
val userAccount: LiveData<Resource<UserAccount>> = userAccountRepository.loadUserAccount(true)
}
any help would be appreciated.
This is caused by an issue with Kotlin kapt on version 1.3.30 issue tracker
change to previous/newer version

Resources