Quarkus 2 vs JUnit TestInfo: classloader issue on annotated test - junit5

In order to make some configurations, I'm reading annotations on tests using JUnit TestInfo.
However I'm facing a problem on Quarkus 2 since TestInfo is loaded with a different classloader than my test, so looking for annotations using testInfo.getTestMethod()...getAnnotationsByType does not return anything.
Consider:
#QuarkusTest
#MyTestConfig("class")
public class AnnotatedTest {
#Test
#MyTestConfig("method")
void test(TestInfo testInfo) {
Class<?> declaringClass = testInfo.getTestMethod().orElseThrow().getDeclaringClass();
System.out.println("declaringClass classloader: " + declaringClass.getClassLoader());
System.out.println("this classloader: " + this.getClass().getClassLoader());
}
}
This gives (Quarkus 2.1.2.Final):
declaringClass classloader: jdk.internal.loader.ClassLoaders$AppClassLoader#3b192d32
this classloader: QuarkusClassLoader:Quarkus Runtime ClassLoader: TEST restart no:0#6a9ad3d6
On Quarkus 1.13.7 both are:
declaringClass classloader: QuarkusClassLoader:Quarkus Base Runtime ClassLoader
this classloader: QuarkusClassLoader:Quarkus Base Runtime ClassLoader
As a consequence, this doesn't work:
#QuarkusTest
#MyTestConfig("class")
public class AnnotatedTest {
#Test
#MyTestConfig("method")
void testProcessAnnotation(TestInfo testInfo) {
assertThat(processAnnotation(testInfo, MyTestConfig.class))
.extracting(MyTestConfig::value).containsExactly("class", "method");
}
<T extends Annotation> List<T> processAnnotation(TestInfo testInfo, Class<T> annotationClass) {
Method m = testInfo.getTestMethod().orElseThrow();
T[] classAnnotations = m.getDeclaringClass().getAnnotationsByType(annotationClass);
T[] methodAnnotations = m.getAnnotationsByType(annotationClass);
return Stream.of(classAnnotations, methodAnnotations)
.flatMap(Arrays::stream)
.collect(Collectors.toList());
}
}

Related

How to mock an SQLiteOpenHelper

I am trying to mock an SQLiteOpenHelper class in instrumented tests so whenever any fragment tries to get information from the database it returns a generic result. However, I keep getting an error saying:
org.mockito.exceptions.base.MockitoException: Cannot mock/spy class
com.example.cleaningschedule.helpers.DatabaseHandler Mockito cannot
mock/spy because :
final class
at com.example.cleaningschedule.ToDoListInstrumentedTest.oneTask(ToDoListInstrumentedTest.kt:81)
The test class is:
#RunWith(AndroidJUnit4::class)
class ToDoListInstrumentedTest {
#Rule
#JvmField var activityRule: ActivityTestRule<MainActivity> = ActivityTestRule(MainActivity::class.java)
private fun getActivity() = activityRule.activity
#After
fun tearDown() {
InstrumentationRegistry.getInstrumentation().getTargetContext().deleteDatabase("TaskDatabase")
}
#Test
fun oneTask() {
val mock = mock(DatabaseHandler::class.java)
`when`(mock.getTasks()).thenThrow()
onView(withId(R.id.taskName)).check(matches(isDisplayed()))
}
}
The class I am trying to mock is:
class DatabaseHandler(context: Context): SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
companion object {
private const val DATABASE_VERSION = 5
private const val DATABASE_NAME = "TaskDatabase"
...
}
override fun onCreate(db: SQLiteDatabase?) {
...
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
...
}
fun getTasks(): MutableList<Pair<MutableList<String>, MutableList<Room>>> {
...
}
}
I have looked at several other similar questions but none have helped:
Error mocking Class which hold reference to SQLiteOpenHelper
Mock final class in java using mockito library - I had a lot of issues with import PowerMock
How to mock a final class with mockito - I have added the dependency and created the file with the mock-maker-inline line as suggested in the answers put I still get the same error. I also tried the answer that suggested Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable)) but this gave me a 'Not enough information to infer type variable T' error
Mock final class with Mockito 2
Mockito cannot mock/spy because : Final Class
Cannot mock/spy class java.util.Optional
I will made an Interface :
public interface ContainerHandler {
MutableList<Pair<MutableList<String>, MutableList<Room>>> getTasks();
}
Then I made DatabaseHandler inherit this interface, I call Mockito's mock function with the Interface.
val mock = mock(ContainerHandler::class.java)
`when`(mock.getTasks()).thenThrow()
And finally I inject my mock into the tested class.

Spock unit testing a Kotlin private static method

I'm looking for help to debug/resolve the obscure error:
java.lang.IllegalArgumentException: wrong number of arguments
I don't want to get into an argument of whether or not I should be testing a private method. But, I'm open to learning how to restructure this into something more testable that isn't exposed to the plugin user. I just can't seem to make it work. Below is the signature of the method and the relevant code.
Output from println in test setup
public static final org.gradle.api.file.DirectoryProperty my.project.MyPlugin.access$getResolvedDir(my.project.MyPlugin,java.lang.Object,org.gradle.api.Project)
Class under test
class MyPlugin : Plugin<Project> {
override fun apply(project: Project): Unit = project.run {
// do some gradle plugin stuff
myTask.configure { dir = getResolvedDir(pluginExtension.dir, project) }
}
private fun getResolvedDir(dir: Any?, project: Project): DirectoryProperty {
// do some stuff to transform the input to a DirectoryProperty
return resolvedDir
}
}
Spock test case
class MyPluginTest extends Specification {
#Rule
public final TemporaryFolder testProjectDir = new TemporaryFolder()
private Project p
private MyPlugin plugin
private CachedMethod getResolvedDirMethod
def setup() {
p = ProjectBuilder.builder().withName("install-plugin-test").build()
plugin = new InstallPlugin()
plugin.metaClass.methods.each {
if (it.name.contains("getResolvedDir"))
getResolvedDirMethod = it
}
getResolvedDirMethod.setAccessible()
println getResolvedDirMethod
}
def "String resolution for dir"() {
when:
def x = "xyz"
then:
DirectoryProperty dir = getResolvedDirMethod.invoke(plugin, x, p)
}
}

Kotlin annotations defined in tests not present in reflection info in integration tests

We have a fairly standard Kotlin DSL Gradle build. We've added an integrationTest sourceSet and task:
plugins {
kotlin("jvm") version "1.3.72"
application
}
sourceSets {
create("integrationTest") {
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
compileClasspath += sourceSets.test.get().output
compileClasspath += sourceSets.main.get().runtimeClasspath
runtimeClasspath += sourceSets.test.get().output
resources.srcDir(sourceSets.test.get().resources.srcDirs)
}
}
val integrationTest = task<Test>("integrationTest") {
description = "Runs the integration tests."
group = "verification"
testClassesDirs = sourceSets["integrationTest"].output.classesDirs
classpath = sourceSets["integrationTest"].runtimeClasspath
mustRunAfter(tasks.test)
useJUnitPlatform()
}
Classes in src/integrationTest/kotlin can use classes from src/test/kotlin just fine, but annotations defined in src/test/kotlin do not show up in reflection data for classes in src/integrationTest/kotlin. When used on classes in src/test/kotlin, the annotations are present in reflection data as expected.
The annotations are very simple:
#Target(FUNCTION, CLASS)
// NB: Default Retention is RUNTIME (Annotation is stored in binary output and visible for reflection)
annotation class SystemProperty(val key: String, val value: String)
// Kotlin does not yet support repeatable annotations https://youtrack.jetbrains.com/issue/KT-12794
#Target(FUNCTION, CLASS)
annotation class SystemProperties(vararg val systemProperties: SystemProperty)
This is how the annotations are used, in a JUnit 5 Extension:
class SystemPropertyExtension : BeforeAllCallback {
override fun beforeAll(extensionContext: ExtensionContext) {
val clazz = extensionContext.requiredTestClass
clazz.getAnnotation(SystemProperty::class.java)?.let {
System.setProperty(it.key, it.value)
}
clazz.getAnnotation(SystemProperties::class.java)?.let {
it.systemProperties.forEach { prop -> System.setProperty(prop.key, prop.value) }
}
}
}
And typical use on the test itself:
#SystemProperty(key = "aws.s3.endpoint", value = "http://localstack:4566")
#ExtendWith(SystemPropertyExtension::class)
class SomeIntegrationTest {
//
}
Setting breakpoints while running tests shows System.setProperty(it.key, it.value) getting called. However while debugging integration tests, the breakpoint is not hit.
Any ideas on what might be wrong/missing here?
We could add a "testing" module to the project and export the test-jar, but would like to avoid that if possible.
The annotations were simply missing #Inherited. They were found on classes, but without #Inherited, they weren't found via superclass.

What is an elegant way to make logger to always prepend the test method name on all logs generated during its execution?

Consider the following setup below:
#Test
class MyTest {
#Test
fun testX(sessionId: String) {
methodName = object {}.javaClass.enclosingMethod.name
LOGGER.info("Test {}: Doing", methodName)
helper(methodName)
LOGGER.info("Test {}: Done", methodName)
}
fun helper(methodName: String) {
LOGGER.info("Test {}: Helping", methodName)
}
}
I would like to know if there is a more elegant way to configure the logger to always prepend the test name (like above) to all loggings done within the scope of the test, s,t. I don't have to pass the method name everywhere.
One option I can think of I can think of is MDC (https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/MDC.html). However, I can see it's not gonna work well in TestNG since the same class instance is shared between test cases (unlike JUnit).
I'd suggest using TestNG listeners for that.
Here is a small example. Create a class implementing ITestListener:
class MyListener: ITestListener {
private val logger: Logger = getLogger(MyListener::class.java)
override fun onTestStart(result: ITestResult?) {
logger.info("Test {}: doing", result?.name)
}
}
Then, register it with this annotation:
import org.testng.annotations.Listeners
import org.testng.annotations.Test
#Listeners(MyListener::class)
class MyTest {
#Test
fun testX() {
// ...
}
#Test
fun testY() {
// ...
}
#Test
fun testZ() {
// ...
}
}
So running your tests you should get something like this:
13:38:57.008 [Test worker] INFO MyListener - Test testX: doing
13:38:57.015 [Test worker] INFO MyListener - Test testY: doing
13:38:57.016 [Test worker] INFO MyListener - Test testZ: doing
Hope this helps

Using Akka Route TestKit with Kotlin Spek

I am trying to test my AkkaHTTP routes (written in Kotlin) using akka-http-testkit. The tests in our project use Spek and I would like to keep it this way.
The Route TestKit tutorial gives a Java example:
public class TestkitExampleTest extends JUnitRouteTest {
TestRoute appRoute = testRoute(new MyAppService().createRoute())
#Test
public void testCalculatorAdd() {
// test happy path
appRoute.run(HttpRequest.GET("/calculator/add?x=4.2&y=2.3"))
.assertStatusCode(200)
.assertEntity("x + y = 6.5")
// test responses to potential errors
appRoute.run(HttpRequest.GET("/calculator/add?x=3.2"))
.assertStatusCode(StatusCodes.NOT_FOUND) // 404
.assertEntity("Request is missing required query parameter 'y'")
// test responses to potential errors
appRoute.run(HttpRequest.GET("/calculator/add?x=3.2&y=three"))
.assertStatusCode(StatusCodes.BAD_REQUEST)
.assertEntity("The query parameter 'y' was malformed:\n" +
"'three' is not a valid 64-bit floating point value")
}
}
The setup uses the testRoute function, which means the test class must extend JUnitRouteTest.
Attempting to translate to a Kotlin Spek test I got this:
class TestKitExampleTest : JUnitRouteTest(), Spek({
describe("My routes") {
val appRoute = testRoute(MyAppService().createRoute())
it("calculator add") {
// test happy path
appRoute.run(HttpRequest.GET("/calculator/add?x=4.2&y=2.3"))
.assertStatusCode(200)
.assertEntity("x + y = 6.5")
//...rest omitted
}
}
})
which does not compile as the class is attempting to inherit two classes. I converted it to the following instead:
class TestKitExampleTest : Spek({
describe("My routes") {
val appRoute = testRoute(MyAppService().createRoute())
it("calculator add") {
// test happy path
appRoute.run(HttpRequest.GET("/calculator/add?x=4.2&y=2.3"))
.assertStatusCode(200)
.assertEntity("x + y = 6.5")
//...rest omitted
}
}
}) {
companion object : JUnitRouteTest()
}
which encouters the runtime error java.lang.IllegalStateException: Unknown factory null
at akka.http.impl.util.package$.actorSystem(package.scala:34).
Is there a way to use Akka's route testkit with Spek? Or is there another way to test these routes?
As #raniejade mentioned above, answered on Github. JUnitRouteTest bootstraps Akka with a rule, but Spek's LifeCycleListener can do the same thing.
Adding the code:
class SpekRouteBootstrapper: LifecycleListener, JUnitRouteTest() {
override fun beforeExecuteTest(test: TestScope) {
systemResource().before()
}
override fun afterExecuteTest(test: TestScope) {
systemResource().after()
}
}
allowed me to do this on the test class:
class TestKitExampleTest: Spek({
val bootstrapper = SpekRouteBootstrapper()
registerListener(bootstrapper)
describe("My routes") {
val appRoute by memoized {
bootstrapper.testRoute(MyAppService().createRoute())
}
it("calculator add") {
// test happy path
appRoute.run(HttpRequest.GET("/calculator/add?x=4.2&y=2.3"))
.assertStatusCode(200)
.assertEntity("x + y = 6.5")
}
}
})