Unit Testing In Kotlin

Unit Testing In Kotlin

Whether you are a disciple of test-driven development or prefer to write your unit tests post development or something else in between, it is imperative that any application is voluminous and comprehensive in terms of automated unit tests. I won’t labour the reasons why as these are well known and understood. Because we should be writing more of these types of tests than any other in the pyramid it’s imperative that we can write them quickly, concisely and that they are easy to read. An easy-to-read test in my mind is one where it's quickly obvious what the test context is, what’s under test and what the expected outcomes are. Kotlin is a language that makes all the above easy and the rest of this piece explores exactly why.

The first consideration we must make when we are writing unit tests in Kotlin is around which library we want to use to write our tests. There are a few options when deciding on this. The ones I have had most experience with thus far are kotlin.test, JUnit5 (Jupiter) and KoTest.

                        
                            import java.time.LocalDateTime
import kotlin.test.Test
import kotlin.test.assertTrue

class LibraryTest {
	private val underTest = Library(LocalDateTime.now())
		
	@Test
	fun `the library should be open for business`() {
		assertTrue { UnderTest.isOpen() }
	}
}
                        
                    

The code below shows the same test written using the Junit5 library, as opposed to kotlin.test. As you’ll observe the look, feel and syntax is exactly same, with the only divergence being with the dependencies imported. This is because kotlin.test is built on top of JUnit. One limitation of kotlin.test in it’s vanilla form is that we can’t leverage the parameterized tests feature that JUnit 5 provides. However, because kotlin.test can work alongside JUnit seamlessly we can in fact leverage this feature by simply adding the JUnit5 jupiter params dependency.

This blending of kotlin.test and JUnit is nice overall as it allows us to leverage the best of both worlds. We can use JUnit features such as parameterised tests and annotations where necessary. However, for when we have very Kotlin specific type scenarios we wish to assert on, such as asserting on coroutine flows we can use kotlin.tests native assertions that fit these situations far better than trying to bend the JUnit assertions that weren’t designed for this.

                        
                            import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.time.LocalDateTime

class LibraryTest {
	private val underTest = Library(LocalDateTime.now())
		
	@Test
	fun `the library should be open for business`() {
		assertTrue { underTest.isOpen() }
	}
}
                        
                    

The third option I have experimented with in my time using Kotlin is KoTest. I must admit my experience with KoTest hasn’t went far beyond mere experimentation, so I won’t claim to be an expert on the nuances of the features it provides. This is no slight on KoTest it’s more having come from a Java background I naturally leaned towards JUnit5 as I knew it well and it done everything I needed it to.

As you will observe from the code snippet below the look and feel of writing the same KoTest is somewhat different.

                        
                            import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.booleans.shouldBeTrue
import java.time.LocalDateTime

class LibraryTest : ShouldSpec({
	val underTest = Library(LocalDateTime.now())
		
	should("be open for business") {
		underTest.isOpen().shouldBeTrue()
	}
})
                        
                    

The thing I like about KoTest and which sparked my interest in looking at it in the first place as the syntax feels more native to Kotlin. Like Kotlin in general, we need to write less code compared to the previous examples and the test definition itself reads more like plain English. Furthermore, KoTest also provides different specs which lend themselves nicely to other types of tests. For example, there is a BehaviorSpec for BDD tests and a FeatureSpec for cucumber feature like tests.

When we start having to write unit tests beyond the scope of our domain logic, the ability to mock out things in layers above or below becomes key. For example, we may want to mock out a database repository function or a call to some external api. In most other languages your only (sensible) port of call is to rely on a mocking framework to do this for you. I’m not saying you shouldn’t also do this in Kotlin but you’re not backed into the same corner. With functions being so easy to pass around in Kotlin you don’t necessarily have to rely on a mocking framework. We can achieve the same thing simply by leaning on the standard library as exemplified below. In this example we are effectively mocking out a create repository call that would persist the book in a database. We use the two variables we assert on to verify the create function is called the correct number of times.

                        
                            import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.time.LocalDateTime

class LibraryTest {
	private val underTest = Library(LocalDateTime.now())
	
	@Test
	fun `we can add a new book to the library`() {
		var createCalled = false
		var createCalledTimes = 0
		val book = Book("Ready Player One")

		underTest.addNew(book) {
    		createCalled = true
    		createCalledTimes++
		}  

		assertTrue(createCalled)
		assertEquals(createCalledTimes, actual: 1)
	}
}		
                        
                    

Let’s look at how we’d use a mocking library to do the same thing, for the same test. For clarity the mocking framework I’ve chosen to use in this example is Mockk which is in my opinion the best Kotlin native one on the block right now.

                        
                            class LibraryTest {		
	private val underTest = Library(LocalDateTime.now())
	
	@Test
	fun `we can add a new book to the library`() {
		val mockCreateCall = mockk<(Book) -> Unit>()
		val book = Book(title: "Ready Player One")
		every { mockCreateCall.invoke(book) } returns Unit
				
		underTest.addNew(book, mockCreateCall)
		verify(exactly = 1) { mockCreateCall.invoke(book) }
	}
}
                        
                    

As you can see above we don’t save a huge amount in terms of lines of code by using a mocking framework as opposed to relying purely on the language, 1 or 2 lines at most in this example.

In terms of a preferred approach I don’t massively favour one over the other, I tend to make the choice based on the experience of the developers on my team. If I am working with seasoned Kotlin professionals who are comfortable with the vagaries of function passing I’ll lean to not bothering with a mocking framework. If I’m working with a team where I’m trying to ramp up their Kotlin expertise I’ll tend to elect to use a mocking framework so as not to overload them with too much complexity right away.

I hope this blog has been useful for those just starting to get to grips with writing unit tests in Kotlin. Going forward from an overall testing point of view I think I’ll explore KoTest much further and use this commercially in my next project. JUnit is great and has stood the test of time, but KoTest is more concise and the big thing that appeals is the varying specs it provides. The BehaviorSpec for example is very attractive in that it provides BDD style test development out of the box without the extraneous overhead that tools such as gherkin and cucumber come with.