Article summary
I’ve recently been playing around with Kotlin in my free time, particularly as an alternative to Java for Android development. Figuring out where to start exploring a new language can be difficult, but knowing that Atomic Object practices test-driven development (TDD), I figured that learning to test in Kotlin would be the best option.
Test Libraries
I’ve put together a list of some libraries and packages that allow you to fully test your Kotlin code. Choosing the right combination of libraries will depend on what you’re comfortable with and what your project requires.
JUnit
Because of its interoperability with Java, JUnit can be used to write tests for your Kotlin code. This could be a good choice if you’ve worked with Java and JUnit before.
Kotlin test
As the standard Kotlin testing framework, kotlin.test provides annotations and assertions, much like JUnit does.
MockK
The MockK mocking library allows you to create mocks more easily in Kotlin. Classes in Kotlin are final by default, and unlike other mocking libraries, MockK allows you to mock out final classes.
Mockito 2
Mockito 2 is another mocking library that allows you to mock final classes.
Annotations
Annotations tell the test runner the function of the methods within your test file. For example, @Test denotes the test function. Adding @Ignore to the same function tells the test runner to skip over it.
Two very helpful annotations for testing are @BeforeTest and @AfterTest. These will be called before each test runs and after each test runs, respectively.
To get the most out of these annotations, use them to set up state for your test, including the subject under test and any mocks, and then to tear down properly when your test completes. In practice, when using mocks, we’ve used the @AfterTest function to verify that the expected methods were called with the correct parameters.
Writing a Test
Because functions are public by default in Kotlin, there’s no need to add a modifier. This means we can simply take the Test annotation, add it to a function, and have a test. Of course, the test does nothing at this point, so let’s add an assertion. These work exactly as they would in Java.
Say you have a very simple function like this:
fun helloWorld(name: String = “World”): String {
return “Hello, ${name}!”
}
You can test it easily as follows:
@Test fun helloWorldReturnsPersonalizedMessage() {
assertEquals(“Hello, Molly!”, helloWorld(“Molly”))
}
@Test fun helloWorldReturnsGenericMessage() {
assertEquals(“Hello, World!”, helloWorld())
}
Of course, Kotlin testing libraries provide the same assertions as any Java library, allowing you to test anything from equality to true/false to whether a set contains certain elements.
Mocking
Much like assertions, you’ll find that Kotlin mocking objects and classes work just like they do in Java.
Say we want to create a mock of a user with a greeting property, and we want to always return the same string. To do this, we can use the following:
val userMock = mockk<User>()
every { userMock.greeting } returns "Hello, World!"
If we initialize our test subject with the userMock, this code says that whenever the greeting property is called on the user, it will return, “Hello, World!” This allows us to isolate the code under test.
Once a test is done running, we can go in and verify that our stubbed properties and methods were called as expected.
verify {
userMock.greeting
}
We can also verify that methods weren’t called, as shown here:
verify {
userMock wasNot Called
}
There are plenty of other features that MockK provides on top of this, so it’s worth exploring and figuring out what is necessary for your tests.
If you’re interested in learning more about mocking, I wrote another blog post covering the general ideas behind it with examples in C#.
Fun with Naming Tests
As you can see in my example tests above, the names of each test are quite lengthy, and the traditional camelcase naming makes them hard to read. That’s unavoidable in Java, but I like to have my test names explain what the test is doing. Fortunately, Kotlin allows you to use spaces in test names as long as you surround the name in backticks. This means that my tests can look like this instead:
@Test fun `helloWorld returns a personalized message`() {
assertEquals(“Hello, Molly!”, helloWorld(“Molly”))
}
@Test fun `helloWorld returns a generic message`() {
assertEquals(“Hello, World!”, helloWorld()
}
Not only does this make reading the names of the tests easier, but it also makes the output of failing tests more readable.
Line 7 on the last code snippet is missing a bracket.
you mean a closing parentheses ;)