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
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.
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)
}
}
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.
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
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")
}
}
})