I have been complaining that I must check the Hibernate queries with my eyes without automation. That's why I am fascinated by the library called Hypersistence Utils by vladmihalcea.
But here is the problem. I am unable to use its methods. I use Kotlin Spring Boot.
Below is the source code:
Entity
#Entity
class Book(
#Id
#GeneratedValue(strategy = IDENTITY)
val id: Long = 0L,
)
Repository
interface BookRepository : JpaRepository<Book, Long>
Service
#Service
class DemoService(
private val bookRepository: BookRepository,
) {
#Transactional
fun a() {
bookRepository.findById(0)
}
}
Finally, test code
#SpringBootTest
#TestConstructor(autowireMode = ALL)
internal class DemoServiceTest(
private val demoService: DemoService,
) {
#Test
fun a() {
reset()
demoService.a()
assertSelectCount(1)
}
}
This test gives me a failing result with the message below:
Expected 1 statements but recorded 0 instead!
com.vladmihalcea.sql.exception.SQLSelectCountMismatchException: Expected 1 statements but recorded 0 instead!
What I don't understand is that, the service method a() under test is transactional one but the assertSelectCount() method does not detect the query having been executed.
Here are the logs:
[ Test worker] org.hibernate.SQL : select book0_.id as id1_0_0_ from book book0_ where book0_.id=?
[ Test worker] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [0]
Could you please help me?
Thanks in advance.
It was data proxy that I missed.
Below codes are needed to pass the test method a(). I have found this here.
#Component
class DatasourceProxyBeanPostProcessor : BeanPostProcessor {
override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any {
return bean
}
override fun postProcessAfterInitialization(bean: Any, beanName: String): Any {
return if (bean is DataSource) {
ProxyDataSourceBuilder.create(bean)
.logQueryBySlf4j(INFO)
.multiline()
.countQuery()
.build()
} else bean
}
}
I found a working integration of vladmihalcea's db-util in the github project Ancarian/Facluties in Spring Boot Test. Adding this Component adds the required listener to the datasource used in your Junit Test : DatasourceProxyBeanPostProcessor.java
Related
I have simple class annotated with #Component and injected repositories like
#Component
class TestsDataPreparer(
private val carRepository: CarRepository
) {
fun withCar(builder: Car.() -> Unit = {}): Car {
return carRepository.save(
Car(
name = builder.name!!
)
)
}
}
which is clear..
But i wonder if it would be ok to do something like this, or if it is considered as anti-pattern.
#Component
class TestsDataPreparer(
val carRepository: CarRepository
) {
fun withCar(builder: Car.() -> Unit = {}): Car {
return carRepository.save(
Car(
name = builder.name!!
)
)
}
}
#Test
fun testCar() {
testsDataPreparer.withCar{this.name="Super-cool-car!"}
assertThat(testsDataPreparer.carRepository.findAll()).hasSize(1)
}
So the question is if it is okay to not inject repository in test class itself, but reuse it from TestsDataPreparer class
Yes, making an originally private field public just for testing can be considered an antipattern. Instead, you can create a CarRepository instance and then pass it to TestsDataPreparer when you create it. But for unit testing, you don't actually need that, you can use a mock and verify that the correct method was called (CarRepository.save).
I am trying to setup an event driven architecture on my Spring Application. I have an interface that blueprints all my processes and I have multiple #Components that inherited by that interface. The below is a simplification of my code:
interface Process<T: Any> {
val type: String
val name: String
fun complete(data: T)
fun cancel(data:T)
}
#Component
class MyProcess(prival val service) : Process<MyProcessDTO>
...
where
class MyProcessDTO {
val id: string
val isComplex: Boolean
}
The thing is that I have more than 10 beans than inherit from Process interface and each of them has a different *DTO type.
My idea would be to make a generic event from the given Process interface and publish the derived event types within each #Component. See my idea below :
class ProcessEvent<T: Process<T>> {
}
#Component
class MyProcess(private val service, val publisher: ApplicationEventPublisher) : Process<MyProcessDTO> {
fun publishEvent(myProcessEvent: ProcessEvent<MyProcessDTO>) {
this.service.makeRequest()
this.publisher.publish(myProcessEvent)
}
}
In that case MyProcess should publish a ProcessEvent with MyProcessDTO type.
Please note that the above is just an approximation of my goal.
Thanks
Im trying to set up a h2 database using jpa/jdbc, after creating an implemntation for a query interface using jpa as opposed to jdbc i am now getting the error:
Parameter 0 of constructor in com.nsa.charitystarter.service.CharityQueries required a single bean, but 2 were found:
- charityRepoJDBC: defined in file [C:\Users\V La Roche\Desktop\assessment-1-starter\out\production\classes\com\nsa\charitystarter\data\CharityRepoJDBC.class]
- charityRepoJPA: defined in null
Im unsure as to what has gone wrong and am not really sure where to go from here, i havent been able to find many people with a similar issue to me online.
My implementation using jdbc
#Repository
public class CharityRepoJDBC implements CharityRepository {
private JdbcTemplate jdbc;
private RowMapper<Charity> charityMapper;
#Autowired
public CharityRepoJDBC(JdbcTemplate aTemplate) {
jdbc = aTemplate;
charityMapper = (rs, i) -> new Charity(
rs.getLong("id"),
rs.getString("name"),
rs.getString("registration_id"),
rs.getString("acronym"),
rs.getString("purpose")
);
}
#Override
public List<Charity> findCharityBySearch(String searchTerm) {
String likeSearch = "%" + searchTerm + "%";
return jdbc.query(
"select id, acronym, name, purpose, logo_file_name, registration_id " +
"from charity " +
"where concat(name, acronym, purpose, registration_id) like ?",
new Object[]{likeSearch},
charityMapper);
}
#Override
public Optional<Charity> findById(Long id) {
return Optional.of(
jdbc.queryForObject(
"select id, acronym, name, purpose, logo_file_name, registration_id from charity where id=?",
new Object[]{id},
charityMapper)
);
}
}
Charity finder implementation:
#Service
public class CharityQueries implements CharityFinder {
private CharityRepository charityRepository;
public CharityQueries(CharityRepository repo) {
charityRepository = repo;
}
public Optional<Charity> findCharityByIndex(Integer index) {
return charityRepository.findById(index.longValue());
}
public List<Charity> findCharityBySearch(String searchTerm) {
return charityRepository.findCharityBySearch(searchTerm);
}
}
CharityFinder interface
public interface CharityFinder {
public Optional<Charity> findCharityByIndex(Integer index);
public List<Charity> findCharityBySearch(String searchTerm);
}
error log :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.nsa.charitystarter.service.CharityQueries required a single bean, but 2 were found:
- charityRepoJDBC: defined in file [C:\Users\V La Roche\Desktop\assessment-1-starter\out\production\classes\com\nsa\charitystarter\data\CharityRepoJDBC.class]
- charityRepoJPA: defined in null
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
Process finished with exit code 0
You have following definition currently,
#Repository
public class CharityRepoJDBC implements CharityRepository {
And you are injecting CharityRepository in your service layer CharityQueries
#Service
public class CharityQueries implements CharityFinder {
private CharityRepository charityRepository;
Hence when you deploy your application the container is confused which bean you are trying to autowire into the service.
By default spring autowires by type and hence by that there are 2 beans which are qualified to be injected by spring container.
CharityRepository itself and other
CharityRepoJDBC
So you need to either explicitly tell container which bean you are trying to autowire in this case.
So you can try adding qualifiers as below to solve the issue.
#Service
public class CharityQueries implements CharityFinder {
#Qualifier("CharityRepoJDBC")
private CharityRepository charityRepository;
and at the same time modify your CharityRepoJDBC to be,
#Repository(value = "CharityRepoJDBC")
public class CharityRepoJDBC implements CharityRepository {
You seem to have the Spring Data JDBC starter on the classpath and the Spring Data JPA starter.
Spring Data JDBC has a bug which causes it to produce implementation for repository interfaces even if it shouldn't, thus you end up with one implementation from JPA and another one from JDBC.
If you really want to use Spring Data JDBC and Spring Data JPA you can limit the #EnableJdbcRepositories and #EnableJpaRepositories annotations using the include and exclude filters.
But from your code and the tags you used I suspect you might be not at all interested in Spring Data Jdbc but only in Spring Jdbc.
If this is the case look for a dependency spring-boot-starter-data-jdbc and change it to spring-boot-starter-jdbc.
In case all this Spring (Data) JDBC/JPA confuse you this question and its answers might help: Spring Data JDBC / Spring Data JPA vs Hibernate
I solved it putting #Primary annotation in the repository interface.
In your case it would be the following:
#Primary
public interface CharityFinder {
public Optional<Charity> findCharityByIndex(Integer index);
public List<Charity> findCharityBySearch(String searchTerm);
}
I’m currently trying to change some Spring configuration properties in test code (they aren’t static, that’s why).
There’s this odd thing when I try to solve my problem with #ContextConfiguration(initializers = [MyTestClass.Initializer::class]).
and in MyTestClass I defined this:
inner class Initializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
override fun initialize(applicationContext: ConfigurableApplicationContext) {
val values = TestPropertyValues.of("spring.datasource.url=" + postgresqlContainer.jdbcUrl)
values.applyTo(applicationContext)
}
}
(I’m using Testcontainers here... how to get this working might be a separate question, feel free to help me out.)
postgresqlContainer is a member of MyTestClass that I want to access. When I run the test I just get an error:
Caused by: java.lang.IllegalArgumentException: No argument provided for a required parameter: instance of fun com.example.MyTestClass.Initializer.<init>(): com.example.MyTestClass.Initializer
Huh, ok, so I kept debugging a bit and I think it’s Spring’s BeanUtils that isn’t able to handle Kotlin inner classes. If I remove the inner keyword from my inner class BeanUtils can create an instance – doesn’t help me of course, since I need access to the property of the outer class.
I wrote a little test to assert my suspicion:
import io.kotlintest.specs.StringSpec
import org.springframework.beans.BeanUtils
class Thing {
inner class InnerThing {
}
}
class BeanUtilTest: StringSpec({
"instantiate inner class" {
BeanUtils.instantiateClass(Thing.InnerThing::class.java)
// fails :-(
}
})
Question: Is there a workaround? How can I override application properties inside my test in Kotlin?
I just ran into this and, after a long time trying to figure out what was going on, I finally came up with a solution.
You can use a companion object as follows (e.g., for MySql):
#Testcontainers
#ExtendWith(SpringExtension::class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(initializers = [ExampleIntegrationTest.Companion.Initializer::class])
class ExampleIntegrationTest {
companion object {
#Container
#JvmField
val mySqlContainer = KotlinMySqlContainer()
class Initializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
override fun initialize(configurableApplicationContext: ConfigurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=" + mySqlContainer.jdbcUrl,
"spring.datasource.username=" + mySqlContainer.username,
"spring.datasource.password=" + mySqlContainer.password
).applyTo(configurableApplicationContext.environment)
}
}
}
...
}
Suppose I have an interface Yoyo and different realizations of this interface:
interface Yoyo {
fun haha() {
println("hello world")
}
}
#Component class Yoyo1 : Yoyo
#Component class Yoyo2 : Yoyo
#Component class Yoyo3 : Yoyo
#Component class YoyoN : Yoyo
Now I would like to instantiate all beans and do some logic after the context has been initialized:
#SpringBootApplication
class YoyoApp
fun main(args: Array<String>) {
SpringApplicationBuilder()
.sources(YoyoApp::class.java)
.initializers(beans {
bean {
CommandLineRunner {
val y1 = ref<Yoyo1>()
val y2 = ref<Yoyo2>()
val y3 = ref<Yoyo3>()
val yN = ref<YoyoN>()
arrayOf(y1, y2, y3, yN).forEach { it.haha() }
}
}
})
.run(*args)
}
Instead of manually getting ref to all beans (which is rather tedious), I would like to do this:
val list = ref<List<Yoyo>>()
list.forEach { it.haha() }
However I get an exception:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.util.List<?>' available
I know I could do this instead, but I would like use the new Kotlin DSL instead:
#Component
class Hoho : CommandLineRunner {
#Autowired
lateinit var list: List<Yoyo>
override fun run(vararg args: String?) {
list.forEach { it.haha() }
}
}
Is it possible? Any ideas?
P.S. Here is the gist.
The context mentioned in the previous answer by #zsmb13 was left internal in favor of the provider<Any>() function (starting with Spring 5.1.1). So in the end I ended up with the following:
interface Yoyo {
fun haha() {
println("hello world from: ${this.javaClass.canonicalName}")
}
}
#Component class Yoyo1 : Yoyo
#Component class Yoyo2 : Yoyo
#Component class Yoyo3 : Yoyo
#Component class YoyoN : Yoyo
#SpringBootApplication
class YoyoApp
fun main(args: Array<String>) {
SpringApplicationBuilder()
.sources(YoyoApp::class.java)
.initializers(beans {
bean {
CommandLineRunner {
val list = provider<Yoyo>().toList()
list.forEach { it.haha() }
}
}
})
.run(*args)
}
The ref function used in the DSL can be found here in the source of the framework. There is no equivalent for getting all beans of a type, but you could add your own extension to the BeanDefinitionDsl class to do this:
inline fun <reified T : Any> BeanDefinitionDsl.refAll() : Map<String, T> {
return context.getBeansOfType(T::class.java)
}
Only problem is that the context required for this is internal in the currently released version of the framework. This commit from 8 days ago makes it publicly available "for advanced use-cases", but there hasn't been a new release of the framework since, so it's not available yet.
(The same commit also makes the class to extend directly the BeanDefinitionDsl class and not BeanDefinitionDsl.BeanDefinitionContext.)
Conclusion: you'll probably have to wait for the next release that includes the commit mentioned above, and then you'll be able to create this extension for yourself. I've also submitted a pull request in hopes that this could be included in the framework itself.