Description
openedon Jul 9, 2024
Describe the bug
I was setting up integrations tests on my Quarkus application using JUnit and RestAssured. The way the application is setup, I'm trying to create basic entity, repository, service and resource that are all called by using the necessary methods and return errors as to be used by the front. The problem is during testing. The first entity I've replaced to use this works perfectly, and the tests implemented performed as expected, but when creating the tests for the second entity, the tests are failing to make the GET.
What I did to recreate the entity (Entity, repository, service, resource and test) was to copy/paste everything, changing the name, and if I start the server normally it works, but during the tests it returns a 404. If copying the first entity's test to the second, the test works, if copying the second entity's test to the first, it fails.
Expected behavior
The request made in the test of the second entity does not return 404.
Actual behavior
The request made in the test of the second entity does is currently returning 404.
How to Reproduce?
I'm not sure how to recreate it.
Output of uname -a
or ver
Microsoft Windows [version 10.0.22631.3737]
Output of java -version
Java not installed, using IntelliJ version (corretto-17 Amazon Corretto 17.0.11)
Quarkus version or git rev
2.6.2.Final
Build tool (ie. output of mvnw --version
or gradlew --version
)
Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae)
Additional information
This is the setup I made:
Basic entity:
@MappedSuperclass
open class BaseEntity(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long = 0
) {
protected val hashMultiplier: Int
get() = 37
@Override
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null) return false
val oEffectiveClass = other.javaClass
val thisEffectiveClass = this.javaClass
if (thisEffectiveClass != oEffectiveClass) return false
return this.toString() == other.toString()
}
@Override
override fun toString(): String {
return Gson().toJson(this) //Does not present as problem currently
}
override fun hashCode(): Int {
return hashMultiplier * id.hashCode() +
hashMultiplier * this::class.simpleName.hashCode() //This gets the name of the classes that implements it
}
}
Entity example:
@Entity(name = "Foo")
data class Foo(
var name: String,
var description: String,
) : BaseEntity() {
override fun hashCode(): Int {
return this.hashCode() +
this.hashMultiplier * name.hashCode() +
this.hashMultiplier * description.hashCode()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
if (!super.equals(other)) return false
other as Foo
if (name != other.name ) return false
if (description!= other.description) return false
return true
}
}
Can't create a single base repository due to panache repositories shenanigans with @Default
, so each class would have this code.
BaseRepository:
@Repository
@Transactional
@RequestScoped
class FooPanacheRepository : PanacheRepository<Foo> {
@Inject
private lateinit var userRepository: UserRepository
@Inject
private lateinit var jwt: JsonWebToken
@Inject
private lateinit var logRepository: LogRepository
fun searh(query: String, params: Map<String, Any>, sort: Sort? = null) = searchQuery(query, params, sort)
fun searchUnique(query: String, params: Map<String, Any>, sort: Sort? = null): Foo{
val data = searchQuery(query, params, sort)
if (data.isEmpty()) throw InternalProjectException()
if (data.size > 1) throw InternalProjectException()
return data.first()
}
fun searchAll(sort: Sort? = null) = searchQuery("", mapOf(), sort)
fun searchById(id: Long): Foo= searchUnique("id = :id", mapOf("id" to id))
fun saveOrUpdate(entity: Foo): Foo=
if (entity.id == 0L) save(entity) else update(entity)
fun deleteThis(entity: Foo) = deleteEntity(entity)
fun deleteThisById(id: Long) = deleteEntity(searchById(id))
private fun searchQuery(query: String, params: Map<String, Any>, sort: Sort? = null): List<Foo> {
try {
return if (sort != null) {
list(query, sort, params)
} else {
list(query, params)
}
} catch (ex: Exception) {
throw InternalProjectException()
}
}
private fun save(entity: Foo): Foo {
try {
persist(entity)
log(entity)
return entity
} catch (ex: Exception) {
throw InternalProjectException()
}
}
private fun update(entity: Foo): Foo{
try {
getEntityManager().merge(entity)
log(entity)
return entity
} catch (ex: Exception) {
throw InternalProjectException()
}
}
private fun deleteEntity(entity: Foo): Boolean {
try {
log(entity)
return deleteById(entity.id)
} catch (ex: Exception) {
throw InternalProjectException()
}
}
}
And entity repositories use it like this:
@Repository
@Transactional
@ApplicationScoped
class FooRepository {
@Inject
private lateinit var repository: FooPanacheRepository
@CacheResult(cacheName = "foo_search")
fun search(@CacheKey query: String, params: Map<String, Any>, @CacheKey sort: Sort? = null) =
repository.search(query, params, sort)
//Other methods created as needed, following this example
}
Services are redirects from the endpoints in the resource, treating the data for the repositories, and resources are created to receive data from the front and send them directly to the service. Both of them have bases created for them implementing the default methods.
This is how the test is implemented:
@QuarkusTest
class FooResourceTest {
@Inject
lateinit var repository: FooPanacheRepository
val url = "/foo"
@Test
fun testSearch() {
val endpoint = "/bar=1" //Will change as needed
val expectedData= repository.search() //Method using direct database access to get expected result
val actualData: foo= `when`()
.request("GET", url + endpoint)
.then()
.statusCode(200)
.extract()
.body().`as`(Foo::class.java)
assertEquals(expectedData, actualData)
}
}
This is replicated to the other tests, and can be changed if needed. Will post more details if needed.