How can I run Nested Test in kotest? - kotlin

I'm trying to migrate test framework from JUnit 5 to kotest.
I selected Annotation Spec at first (to check compatibility), but Nested Annotation doesn't work as expected
Sample code is below
class Outside : AnnotationSpec() {
#Test
fun runOutside() { }
#Nested
inner class Inside() {
#Test
fun runInside() { }
}
}
runOutside runs well, but runInside doesn't. The error message is java.lang.IllegalArgumentException: object is not an instance of declaring class
What's wrong ? is this a bug ?

Related

Kotlin expect generic subclass as parameter

I have an interface which contains a generic and have its extensions working properly, however I'm not able to receive a list of this subclasses as parameter.
The code below works perfectly:
interface Runnable
class FirstRunnable : Runnable
class SecondRunnable : Runnable
interface Runner<in T> where T : Runnable {
fun run(runnable: T)
}
class FirstRunner : Runner<FirstRunnable> {
override fun run(runnable: FirstRunnable) = println("first runner")
}
class SecondRunner : Runner<SecondRunnable> {
override fun run(runnable: SecondRunnable) = println("second runner")
}
The problem comes in the block below:
class ListRunner(private val runners: List<Runner<Runnable>>)
val runner = ListRunner(listOf(FirstRunner(), SecondRunner()))
ListRunner does not accept FirstRunner() and SecondRunner() as parameters and complains with:
Type mismatch.
Required:
List<Runner<Runnable>
Found:
List<Runner<{FirstRunnable & SecondRunnable}>>
I want to inject the list into the ListRunner to be able to run in the entire list at once, within the runner I have a rule to run only accepted Runnable
Solution
Both answers helped me to find out the solution,
As pointed by Nishant Jalan, I had first to add out variance to the ListRunner
class ListRunner(private val runners: List<Runner<out Runnable>>)
And as Sweeper says:
It is not safe to put anything there. The Kotlin type system is smart
enough that it tells you this by saying that run there takes the type
Nothing.
So the solution was adding #UnsafeVariance annotation to the Runner interface:
interface Runner<in T> where T : Runnable {
fun run(runnable: #UnsafeVariance T)
}
Still it is unsafe and the annotation only prevents the compiler to complain about it, however I have a previous verification which guarantee the runnable is run in the correct runner.
You can use a star-projection to say you want a list of any kind of Runner:
class ListRunner(private val runners: List<Runner<*>>)
This will cause the following to compile:
val runner = ListRunner(listOf(FirstRunner(), SecondRunner()))
However, this will also prevent you from running any of the runners in ListRunner. For example, you cannot do this in ListRunner:
fun runAll() {
for (runner in runners) {
runner.run(...)
}
}
Because what actually is the ... part? It is not safe to put anything there. The Kotlin type system is smart enough that it tells you this by saying that run there takes the type Nothing.
You can't pass a FirstRunnable, because runner could be a SecondRunner, which takes a SecondRunnable. You can apply a similar reasoning for why you can't pass a SecondRunnable, or any other Runnable. ListRunner don't know what kind of Runners are in the list, so it can't run it, because different Runners needs different Runnables to run.
Of course, you can check the types of the runner first, then give them the correct kind of runnable:
for (runner in runners) {
when (runner) {
is FirstRunner -> runner.run(FirstRunnable())
is SecondRunner -> runner.run(SecondRunnable())
else -> println("I didn't expect this type of runner!")
}
}
Note that you need an else branch, in case someone passed in a Runner that isn't any of the types you are checking. Anyone can implement your interface, after all!
If you want to eliminate the else branch, you can make Runner sealed:
sealed interface Runner<in T> where T : Runnable {
There are a few things you can tweak in your code to perform the operation that you are looking for.
interface Runner<in T> where T : Runnable can simply be reduced to interface Runner<T : Runnable>. They both do the same thing.
While declaring the ListRunner class, you need to pass a List of Runner of a type that is a Runnable. Hence, you need to replace your type with List<Runner<out Runnable>>
The final code is written below.
interface Runnable
class FirstRunnable : Runnable
class SecondRunnable : Runnable
interface Runner<T : Runnable> {
fun run(runnable: T)
}
class FirstRunner : Runner<FirstRunnable> {
override fun run(runnable: FirstRunnable) = println("first runner")
}
class SecondRunner : Runner<SecondRunnable> {
override fun run(runnable: SecondRunnable) = println("second runner")
}
class ListRunner(private val runners: List<Runner<out Runnable>>)

How to create parameterized hooks in junit 5?

I am writing a test in using junit 5 where I want to run a same test on different sets of data. To achieve that, I am using parameterization. Now a situation came in where I want to run something before each set. I want to implement that in #BeforeEach hook and after some research, I found that ParameterResolver implementation can be one of the solution for that. But with that, test is getting issues to get parameter in #BeforeEach hook.
Here I am adding my code. These are kotlin classes. One test class StringDataClientTest which extends StringDataTest in which I am adding test parameter and #beforeEach hook. Adding #ExtendWith with this class which is extending StringParameterResolver class.
StringDataClientTest:
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class StringDataClientTest : StringDataTest() {
#DisplayName("First Client Test")
#ParameterizedTest
#MethodSource("getString")
fun firstClientTest(data: String) {
System.out.println("First client test for: $data")
}
}
StringDataTest:
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.provider.MethodSource
#ExtendWith(StringParameterResolver::class)
open class StringDataTest {
companion object {
#JvmStatic
fun getString(): List<String> {
return listOf(
"Parameter 1",
"Parameter 2",
"Parameter 3",
"Parameter 4"
)
}
}
#BeforeEach
#MethodSource("getString")
fun beforeEveryTest(value: String) {
System.out.println(" --------- Running before hook for: $value")
}
}
StringParameterResolver:
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
class StringParameterResolver : ParameterResolver {
override fun supportsParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Boolean {
return parameterContext!!.parameter.type === StringDataTest::class.java
}
override fun resolveParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Any {
return StringDataTest()
}
}
And I am getting this exception in console output:
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [java.lang.String arg0] in method [public final void com.thescore.test.cases.team.testCode.StringDataTest.beforeEveryTest(java.lang.String)].
Expecting:
Running before hook for: Parameter 1
First client test for: Parameter 1
Running before hook for: Parameter 2
First client test for: Parameter 2
Running before hook for: Parameter 3
First client test for: Parameter 3
Running before hook for: Parameter 4
First client test for: Parameter 4
Haven't done parameterized hooks before. Please help with this. Thanks!

How to apply a Rule to all test cases in a AndroidJUnitRunner?

I'm trying to apply a TestWatcher as a rule across all my test cases run by a particular runner.
MetadataCollector:
class MetadataCollector : TestWatcher() { ... }
TestRunner:
class TestRunner : AndroidJUnitRunner() {
override fun onCreate(arguments: Bundle?) {
super.onCreate(arguments)
}
override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application {
return super.newApplication(cl, TestApplication::class.java.name, context)
}
}
All of my test classes currently require MetadataCollector() to be initialized as a Rule:
Test Class:
#JvmField #Rule val collector = MetadataCollector()
Is there a way I can create an instance of this rule automatically to each test case from the runner? Ideally, this is to avoid duplicating this #Rule creation in every single Test Class.
I am unfortunately stuck with JUnit 4 at the moment. :(
There is a better way to do this, by injecting an instance of RunListener into your test runner. In your gradle config, you need to:
defaultConfig.testInstrumentationRunner = "com.mypackage.MyTestRunnerClassName"
defaultConfig.testInstrumentationRunnerArgument("listener", "com.mypackage.MyRunListenerClassName")
And in code, create a RunListenerClassName to implement the corresponding hooks
class MyRunListener : RunListener() {
// impl
}

How to create a TestContainers base test class in Kotlin with JUnit 5

I am trying to use Neo4j TestContainers with Kotlin, Spring Data Neo4j, Spring Boot and JUnit 5. I have a lot of tests that require to use the test container. Ideally, I would like to avoid copying the container definition and configuration in each test class.
Currently I have something like:
#Testcontainers
#DataNeo4jTest
#Import(Neo4jConfiguration::class, Neo4jTestConfiguration::class)
class ContainerTest(#Autowired private val repository: XYZRepository) {
companion object {
const val IMAGE_NAME = "neo4j"
const val TAG_NAME = "3.5.5"
#Container
#JvmStatic
val databaseServer: KtNeo4jContainer = KtNeo4jContainer("$IMAGE_NAME:$TAG_NAME")
.withoutAuthentication()
}
#TestConfiguration
internal class Config {
#Bean
fun configuration(): Configuration = Configuration.Builder()
.uri(databaseServer.getBoltUrl())
.build()
}
#Test
#DisplayName("Create xyz")
fun testCreateXYZ() {
// ...
}
}
class KtNeo4jContainer(val imageName: String) : Neo4jContainer<KtNeo4jContainer>(imageName)
How can I extract the databaseServer definition and the #TestConfiguration? I tried different ways of creating a base class and having the ContainerTest extend it, but it is not working. From what I understand, static attriubutes are not inherited in Kotlin.
Below my solution for sharing same container between tests.
#Testcontainers
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
abstract class IntegrationTest {
companion object {
#JvmStatic
private val mongoDBContainer = MongoDBContainer(DockerImageName.parse("mongo:4.0.10"))
.waitingFor(HostPortWaitStrategy())
#BeforeAll
#JvmStatic
fun beforeAll() {
mongoDBContainer.start()
}
#JvmStatic
#DynamicPropertySource
fun registerDynamicProperties(registry: DynamicPropertyRegistry) {
registry.add("spring.data.mongodb.host", mongoDBContainer::getHost)
registry.add("spring.data.mongodb.port", mongoDBContainer::getFirstMappedPort)
}
}
}
The key here is to not use #Container annotation as it will close just created container after your first test subclass executes all tests.
Method start() in beforeAll() initialize container only once (upon first subclass test execution), then does nothing while container is running.
By theory we shouldn't have to do this hack, based on:
https://www.testcontainers.org/test_framework_integration/junit_5/
...container that is static should not be closed until all of tests of all subclasses are finished, but it's not working that way and I don't know why. Would be nice to have some answer on that :).
I've had the same issue (making Spring Boot + Kotlin + Testcontainers work together) and after searching the web for (quite) a while I found this nice solution: https://github.com/larmic/testcontainers-junit5. You'll just have to adopt it to your database.
I faced very similar issue in Kotlin and spring boot 2.4.0.
The way you can reuse one testcontainer configuration can be achieved through initializers, e.g.:
https://dev.to/silaev/the-testcontainers-mongodb-module-and-spring-data-mongodb-in-action-53ng or https://nirajsonawane.github.io/2019/12/25/Testcontainers-With-Spring-Boot-For-Integration-Testing/ (java versions)
I wanted to use also new approach of having dynamicProperties and it worked out of a boxed in java. In Kotlin I made sth like this (I wasn't able to make #Testcontainer annotations working for some reason). It's not very elegant but pretty simple solution that worked for me:
MongoContainerConfig class:
import org.testcontainers.containers.MongoDBContainer
class MongoContainerConfig {
companion object {
#JvmStatic
val mongoDBContainer = MongoDBContainer("mongo:4.4.2")
}
init {
mongoDBContainer.start()
}
}
Test class:
#SpringBootTest(
classes = [MongoContainerConfig::class]
)
internal class SomeTest {
companion object {
#JvmStatic
#DynamicPropertySource
fun setProperties(registry: DynamicPropertyRegistry) {
registry.add("mongodb.uri") {
MongoContainerConfig.mongoDBContainer.replicaSetUrl
}
}
}
Disadvantage is this block with properties in every test class what suggests that maybe approach with initializers is desired here.

Kotlin SAM Runtime Error: NoSuchMethodError: No static method

Today I ran into a really strange runtime error while developing kotlin / android that involves SAM conversions and sub classing.
Here's a minimal example of pure java + kotlin. Here are two java classes:
public class A {
public interface I {
public void f();
}
public I i;
}
public class B extends A {}
And here is a kotlin main function:
fun main(args: Array<String>) {
A().i = B.I {}
}
This code compiles fine but at run time I get the following error:
Exception in thread "main" java.lang.NoSuchMethodError: B.I(Lkotlin/jvm/functions/Function0;)LA$I;
at MainKt.main(Main.kt:2)
Now, this is already bad -- if code like this does not work (it never will I guess) the compiler should raise an error. But at least one could say that it is bad idea to reference to the interface I via the subclass B instead of the place of definition A (i.e., A.I).
It's less clear though, if this code is in a sub class of B where I can reference I directly using I:
class C: B {
constructor() {
this.i = I {}
}
}
So my questions would be:
Why is this behavior happening at all?
If it is happening, why is the compiler not raising an error already?
PS: In android the error message looks similar to this, which is even more confusing:
Caused by: java.lang.NoSuchMethodError: No static method OnFocusChangeListener(Lkotlin/jvm/functions/Function2;)Landroid/view/View$OnFocusChangeListener; in class Landroid/widget/LinearLayout; or its super classes (declaration of 'android.widget.LinearLayout' appears in /system/framework/framework.jar:classes2.dex)
Define main method as static like-
companion object {
#JvmStatic fun main(args: Array<String>) {
A().i = B.I {}
}
}