How can I initialize variable before each test using kotlin-test framework - kotlin

I'm trying to find a way to set up variable before each test. Just like the #Before method in Junit. Go through the doc from kotlin-test, I found that I can use interceptTestCase() interface. But unfortunately, the code below will trigger exception:
kotlin.UninitializedPropertyAccessException: lateinit property text has not been initialized
class KotlinTest: StringSpec() {
lateinit var text:String
init {
"I hope variable is be initialized before each test" {
text shouldEqual "ABC"
}
"I hope variable is be initialized before each test 2" {
text shouldEqual "ABC"
}
}
override fun interceptTestCase(context: TestCaseContext, test: () -> Unit) {
println("interceptTestCase()")
this.text = "ABC"
test()
}
}
Am I in the wrong way to use interceptTestCase()?
Thank you very much~

A quick solution is to add below statement in test case:
override val oneInstancePerTest = false
The root cause is that oneInstancePerTest is true by default(although it's false in kotlin test doc), which means every test scenario will run in the different instances.
In the case in question,
The initializing interceptTestCase method ran in instance A, set text to ABC. Then the test case ran in instance B without interceptTestCase.
For more detail, there is an open issue in GitHub:
https://github.com/kotlintest/kotlintest/issues/174

You have not initialized the text variable.
init called first when you create an object for a class.
You are calling text shouldEqual "ABC" in init block in your code, that time there will be no value in a text variable.
Your function interceptTestCase(context: TestCaseContext, test: () -> Unit) only can be called after the init block.
Initialize the text at the constructor itself like below code, so you won't get this error or make some alternative.
class KotlinTest(private val text: String): StringSpec()

Related

Kotlin script - "extension method" is a member and an extension at the same time

I have Kotlin some code that works as a class but when I try and run it as a Kotlin script I am getting " error: 'getNameAndVersion' is a member and an extension at the same time. References to such elements are not allowed"
enum class Env { Test, Sit }
data class ImageVersions(val apiName: String, val versions: Map<Env, String?>)
fun String.getNameAndVersion() = substringBefore(':') to substringAfter(':')
val testList = listOf("api-car-v1:0.0.118", "api-dog-v1:0.0.11", "api-plane-v1:0.0.36")
val sitList = listOf("api-car-v1:0.0.119", "api-plane-v1:0.0.37", "api-dog-v1:0.0.12")
getVersions(
mapOf(
Env.Test to testList,
Env.Sit to sitList
)
).forEach(::println)
fun getVersions(envMap: Map<Env, List<String>>): List<ImageVersions> {
val envApiNameMap = envMap.mapValues { it.value.associate(String::getNameAndVersion) }
val allApiNames = envApiNameMap.flatMap { it.value.keys }.distinct()
return allApiNames.map { apiName ->
ImageVersions(apiName, envApiNameMap.mapValues { it.value[apiName] })
}
}
I don't think I'm doing anything wrong with the way I'm using the method reference but according to my compiler I'm wrong. Appreciate some help with this. thanks
kotlinc -script .\myscript.kts
error: 'getNameAndVersion' is a member and an extension at the same time. References to such elements are not allowed
I don't have any experience with scripts but this error occurs when you try to reference a function inside a class that is also an extension function. (Here it is pointing to String::getNameAndVersion). Maybe when you run a script, the entire code is wrapped inside a class and then executed.
To fix this you can do one of the following:
Convert the function to a normal function which accepts a String parameter.
fun getNameAndVersion(s: String) = s.substringBefore(':') to s.substringAfter(':')
And replace String::getNameAndVersion with just ::getNameAndVersion in associate function
Other option is to directly call the function in the associate's lambda instead of passing a reference of this function.
.associate { it.getNameAndVersion() }

How to test add method in Kotlin object Singleton class

I'm trying to do some testing in my object kotlin class, but I can getting an error in my thenReturn method when I try to pass the object. I get a Require: Unit! Found checkout. Someone can point me how is possible to test it??
If I remove thenReturn method I get this error:
Hints:
1. missing thenReturn()
2. you are trying to stub a final method, which is not supported
3: you are stubbing the behaviour of another mock inside before 'thenReturn' instruction if completed
I refer this link but I can't get it.
object CheckoutRepository: CheckoutContract.Model {
var checkout: MutableList<Checkout> = mutableListOf<Checkout>()
override fun addProductToShoppingCart(checkoutProduct: Checkout){
checkout.add(checkoutProduct)
}
override fun getProductsInShoppinCart() : List<Checkout>?{
return checkout
}
override fun cleanCheckout(){
checkout.clear()
}
}
#Test
fun test_with_mock() {
val mock = mock<CheckoutContract.Model>()
var checkout = Checkout("VOUCHER", "voucher", 35.0, 5)
mock.addProductToShoppingCart(checkout)
val answer = mock.getProductsInShoppinCart()
`when`(mock.addProductToShoppingCart(checkout)).thenReturn(checkout)
assertNotNull(checkout)
assertEquals(checkout, answer)
}
In your function addProductToShoppingCart don't have any return type.
if you want check Checkout class make the change in the functions then test case will pass
override fun addProductToShoppingCart(checkoutProduct: Checkout):Checkout{
checkout.add(checkoutProduct)
return checkout
}

Kotlintest interceptor and lateinit vars

I have some testcases that share a common setup. They all need two fields which can be initialized in the same way. So I thought I can extract them into lateinit var fields and create them in an test-case-interceptor.
But when I try to access them in my testcases they always throw an exception because they are not initialized.
Is there a way to create the fields before every testcase?
Here is my code so far:
class ElasticsearchFieldImplTest : WordSpec() {
// These 2 are needed for every test
lateinit var mockDocument: ElasticsearchDocument
lateinit var mockProperty: KProperty<*>
override fun interceptTestCase(context: TestCaseContext, test: () -> Unit) {
// Before Each
mockDocument = mock()
mockProperty = mock {
on {name} doReturn Gen.string().generate()
}
// Execute Test
test()
// After Each
}
init {
"ElasticsearchFields" should {
"behave like normal var properties" {
val target = ElasticsearchFieldImpl<Any>()
// Here the exception is thrown
target.getValue(mockDocument, mockProperty) shouldBe null
val testValue = Gen.string().generate()
target.setValue(mockDocument, mockProperty, testValue)
target.getValue(mockDocument, mockProperty) shouldBe testValue
}
}
}
}
When I step through it with a debugger and set a breakpoint in the interceptTestCase methods I see that it is executed before the test and that the properties are initialized. Then I step forward to the test and in it the properties are not initialized anymore.
Клаус Шварц's answer is incorrect. This is not how kotlintest works - in init lambdas are only created, not run. So you are not accessing your lateinit vars in init block. They just never have any value assigned.
This doesn't work because of bug in kotlintest, described (and actually almost resolved) here: https://github.com/kotlintest/kotlintest/issues/174
In short - interceptTestCase is called on different instance of class than real tests. So it has no influence on your tests at all.
Workaround is to override property:
override val oneInstancePerTest = false
Then there is only one instance and interceptTestCase works correctly, but you have to remember - there is only one instance for all tests.
Kotlintest 3.0 will be free of this bug. (But possibly might have one instance for all tests by default.)
You should not access lateinit vars before they were initialized.
The problem is that you are accessing your lateinit vars inside init {} block, which is default constructor and it is called before interceptTestCase().
The easiest way here is just to make mockDocument and mockProperty nullable.
var mockDocument: ElasticsearchDocument? = null
var mockProperty: KProperty<*>? = null
and if you want you test to crash if these fields were not initialized add !! modifier:
init {
"ElasticsearchFields" should {
"behave like normal var properties" {
val target = ElasticsearchFieldImpl<Any>()
// Here the exception is thrown
target.getValue(mockDocument!!, mockProperty!!) shouldBe null
val testValue = Gen.string().generate()
target.setValue(mockDocument!!, mockProperty!!, testValue)
target.getValue(mockDocument!!, mockProperty!!) shouldBe testValue
}
}
}

How to write a package-level static initializer in Kotlin?

A previous question shows how to put a static initializer inside a class using its companion object. I'm trying to find a way to add a static initializer at the package level, but it seems packages have no companion object.
// compiler error: Modifier 'companion' is not applicable inside 'file'
companion object { init { println("Loaded!") } }
fun main(args: Array<String>) { println("run!") }
I've tried other variations that might've made sense (init on its own, static), and I know as a workaround I can use a throwaway val as in
val static_init = {
println("ugly workaround")
}()
but is there a clean, official way to achieve the same result?
Edit: As #mfulton26's answer mentions, there is no such thing as a package-level function really in the JVM. Behind the scenes, the kotlin compiler is wrapping any free functions, including main in a class. I'm trying to add a static initializer to that class -- the class being generated by kotlin for the free functions declared in the file.
Currently there is no way to add code to the static constructor generated for Kotlin file classes, only top-level property initializers are getting there. This sounds like a feature request, so now there is an issue to track this: KT-13486 Package-level 'init' blocks
Another workaround is to place initialization in top-level private/internal object and reference that object in those functions that depend on the effect of that initialization. Objects are initialized lazily, when they are referenced first time.
fun dependsOnState(arg: Int) = State.run {
arg + value
}
private object State {
val value: Int
init {
value = 42
println("State was initialized")
}
}
As you mentioned, you need a property with something that would run on initialisation:
val x = run {
println("The package class has loaded")
}
I got around it by using a Backing Property on the top-level, under the Kotlin file. Kotlin Docs: Backing Properties
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // Type parameters are inferred
// .... some other initialising code here
}
return _table ?: throw AssertionError("Set to null by another thread")
}

How do I initialize a final field in Kotlin?

Let's say I declared a final field with private final String s (Java) or val s (Kotlin). During initialization I want to initialize the field with the result of a call to a remote service. In Java I would be able to initialize it in the constructor (e.g. s = RemoteService.result()), but in Kotlin I can't figure out how to do that because as far as I can tell the field has to be initialized in the same line it's declared. What's the solution here?
You can set val value in init block:
class MyClass {
val s: String
init {
s = "value"
}
}
You can also initialize the value with by lazy the value will be initialized the first time it is referred. An example
val s by lazy { RemoteService.result() }
kotlin will guess the type of s from the return type of the expression.
You can use run:
class MyClazz {
val prop = run {
// do stuff
// do stuff again
123 // return expression
}
}
From the docs (emphasis is mine):
Besides calling run on a receiver object, you can use it as a non-extension function. Non-extension run lets you execute a block of several statements where an expression is required.
It has been possible to do it simply like this since the very first official stable release of Kotlin:
class MyClass {
val s = RemoteService.result()
}