https://kotlinlang.org logo
#getting-started
Title
# getting-started
d

Dron Bhattacharya

02/21/2023, 3:05 PM
how to write a test for this class:
Copy code
class Task(var title: String, bodyText: String = "") {
    var body: String = bodyText
        get() {
            return if (this.body.isEmpty()) "NO BODY FOUND"
            else this.body
        }
    fun printTask() {
        println("Task: $title\n")
        println("Body:")
        println(body)
    }
}
The current TaskTest.kt file:
Copy code
import org.junit.jupiter.api.Test

import org.junit.jupiter.api.Assertions.*

class TaskTest {
    val testSample1: Task = Task("Learn Encapsulation")
    val testSample2: Task = Task("Learn Encapsulation", "Encapsulation is good for reducing change")
    @Test
    fun printTask() {

    }
}
s

Sam

02/21/2023, 3:06 PM
It’s hard to test this because
println
is an example of a function that operates via side effects — i.e. it does something, instead of returning something.
plus1 2
m

mkrussel

02/21/2023, 3:07 PM
You could replace
System.out
with your own object that builds a string.
d

Dron Bhattacharya

02/21/2023, 3:09 PM
@mkrussel I cannot understand your suggestion. Can you elaborate a little bit?
m

mkrussel

02/21/2023, 3:12 PM
I've not done this in Kotlin, but Java has https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#setOut-java.io.PrintStream- You can use that to redirect
System.out
which the
println
function uses. You then create a
PrintStream
that takes an
ByteArrayOutputStream
, you can then convert those bytes to a String to validate the printing.
Then revert
System.out
back to the original print stream.
s

Sam

02/21/2023, 3:12 PM
Possibly a simpler version of the same thing might be something like this:
Copy code
class Task(var title: String, bodyText: String = "") {
    var body: String = bodyText
        get() {
            return if (this.body.isEmpty()) "NO BODY FOUND"
            else this.body
        }
    fun printTask() {
        printer("Task: $title\n")
        printer("Body:")
        printer(body)
    }

    companion object {
        var printer: (String) -> Unit = ::println
    }
}

class TaskTest {
    val testSample1: Task = Task("Learn Encapsulation")
    val testSample2: Task = Task("Learn Encapsulation", "Encapsulation is good for reducing change")
    @Test
    fun printTask() {
        val output = mutableListOf<String>()
        Task.printer = { output.add(it) }
        testSample1.printTask()
        assertTrue(output.contains("Learn Encapsulation"))
    }
}
Easier to replace a
(String) -> Unit
function than the whole system output stream.
Not the sort of thing I would want to do in real code, but good enough for learning
m

mkrussel

02/21/2023, 3:14 PM
I would probably go with making the printer a constructor argument versus changing global state.
💯 1
s

Sam

02/21/2023, 3:14 PM
Yep, would definitely do that if I was going to use something like this for real
a

Adam S

02/21/2023, 3:22 PM
generally what applications do is use a Logger rather than printing directly to console. There are libraries that provide loggers, but you don't need a library as @Sam & @mkrussel demoed - the most basic logger I can think of is just a type alias
Copy code
typealias Logger = (String) -> Unit
So anywhere you want to print, pass in a Logger
Copy code
typealias Logger = (String) -> Unit

class Task(
  private val logger: Logger
) { 

  fun printTask() {
      logger("Task: ...")
      logger("Body: ...")
  }
}
👍 1
and it's perfectly fine to print to stdout - so you can set that up as the default behaviour
Copy code
class Task(
  private val logger: Logger = ::println
)
that uses a function reference to
println()
, so for every message it will just print to stdout
and then in tests you override the default logger to capture the log messages by, for example, collecting them into a list
Copy code
fun main() {
    // collect the log messages into this list
    val logMessages = mutableListOf<String>()
    
    // override the default logger
    val task = Task(logger = logMessages::add)
    
    task.printTask()
    
    // now we can access all captured messages
    println("captured messages $logMessages")
}
So that ^ will collect all your log messages into a list. Now each test can be run independently, and the logs won't be muddled up.
Here's a runnable example that I updated to use a more object-oriented approach, which I think is more readable. run in Kotlin Playground
k

Klitos Kyriacou

02/21/2023, 3:49 PM
By the way, if you are trying to access the backing field, you should use
field
. Otherwise this will cause an endless recursion and eventually a stack overflow:
Copy code
var body: String = bodyText
        get() {
            return if (this.body.isEmpty()) "NO BODY FOUND"
            else this.body
        }
More details: https://kotlinlang.org/docs/properties.html#backing-fields
💯 1
👆 1
t

Ties

02/21/2023, 4:11 PM
I just offered to explain things like side-effects and how it impacts testability in a call 😛 might be more effective than slack messages
🌟 1
d

Dron Bhattacharya

02/21/2023, 5:31 PM
I have ended up writing this. Is it OK? Task.kt:
Copy code
class Task(var title: String, private val bodyText: String = "") {
    var body: String = bodyText
        get() = field.ifEmpty { "NO BODY FOUND" }
    fun printTask(): String {
        val text: String = """
                Task: $title
                
                Body:
                $body
            """.trimIndent().also {
            println(it)
        }
        return text
    }
}
TaskTest.kt:
Copy code
class TaskTest {
    val titles = listOf<String>("Learn Encapsulation", "Learn interface")
    val bodys = listOf<String>("NO BODY FOUND", "Interface is good for reducing change")

    val testSample1: Task = Task(titles[0])
    val testSample2: Task = Task(titles[1], bodys[1])

    @Test
    fun printTask_NoBody() {
        val expected =
            """
                Task: ${titles[0]}

                Body:
                ${bodys[0]}
            """.trimIndent()
        assertEquals(expected, testSample1.printTask())
    }

    @Test
    fun printTask() {
        val expected =
            """
                Task: ${titles[1]}

                Body:
                ${bodys[1]}
            """.trimIndent()
        assertEquals(expected, testSample2.printTask())
    }
}
👍 1
Is there anything more to test for this class, like
instance creation
of the class? I am new to this
I am using a String template in the
printTask()
function. But when I change the template, I have to change it manually in the test class too. I have to change the template in 3 places. I know it can be better, but what to apply here is not clear to me (abstract class, interface, or something else). Can I do anything here?
I have tried to solve the "manually modifying String template" issue in this way: Now if I have to modify the String that gets printed, I can change it in 1 place and it reflects in other places. Task.kt:
Copy code
import java.util.UUID

class Task(var title: String, bodyText: String = "") {

    val id = UUID.randomUUID().hashCode()
    var body: String = bodyText
        get() = field.ifEmpty { "NO BODY FOUND" }

    init {
        println("Task created with ID=$id")
    }

    fun printTask() =
        TaskPrintString(title, body).formattedString.also {
            println(it)
        }
}

data class TaskPrintString(private val title: String, private val body: String) {
    val formattedString: String =
        """
            
            --------------------               
            Task: $title
            --------------------
            Body:
            $body
            
        """.trimIndent()
}
TaskTest.kt:
Copy code
class TaskTest {
    val titles = listOf<String>("Learn Encapsulation", "Learn interface")
    val bodys = listOf<String>("NO BODY FOUND", "Interface is good for reducing change")


    @Test
    fun printTask_NoBody() {
        val testSample1: Task = Task(titles[0])
        val expected = TaskPrintString(titles[0], bodys[0]).formattedString
        assertEquals(expected, testSample1.printTask())
    }

    @Test
    fun printTask_withBody() {
        val testSample2: Task = Task(titles[1], bodys[1])
        val expected = TaskPrintString(titles[1], bodys[1]).formattedString
        assertEquals(expected, testSample2.printTask())
    }
}
3 Views