I am using MockK
(https://mockk.io/) for a while now in all my Kotlin applications. But just recently I discovered some features I wish I knew beforehand because they really reduce the verbosity of my unit test.
As a developer I am lazy. Therefore I just looked up MockK features I needed at the time without discovering all the other mentioned advanced features. I felt like I will find them when I need them. To be honest - I should have spent some time reading the documentation. Next time I definitely will!
Here is a simple example that improved in readability thanks to some research on MockK features.
Logic to Test
Here is the logic I wanted to test
fun dispatchEvent() {
val taskPayload = Json.encodeToString(TaskPayload("123"))
val gameEvent: GameEvent = GameEvent(UUID.randomUUID().toString(), "task", DateTime.now(), taskPayload)
val topic = "projects/barbarus-game/topics/task"
LOG.info("dispatch event to topic $topic")
pubSubTemplate.publish(topic, gameEvent).addCallback({ LOG.info(it) }, { LOG.error("failure") })
}
Unit Test Iteration #0
And here is my initial unit test:
internal class EventDispatcherTest {
private val pubSubTemplate: PubSubTemplate = mockk()
private val eventDispatcher: EventDispatcher = EventDispatcher(pubSubTemplate)
@Test
fun `should publish game event message`() {
val testTopic = "projects/barbarus-game/topics/task"
every { pubSubTemplate.publish(eq(testTopic), any<GameEvent>()) } returns mockk() {
every { addCallback(any(), any()) } returns Unit
}
eventDispatcher.dispatchEvent()
verify { pubSubTemplate.publish(eq(testTopic), any<GameEvent>()) }
}
}
I simply want to see if the dispatchEvent()
function does call the PubSubTemplate#publish
function as expected.
However MockK#verify
does not work withou the pubSubTemplate
mock being defined with a proxy. To satisfy the test runner I added an every
for pubSubTemplate
and also had to satisfy the return value (or answer in mockk-domain language). The return value is of type ListenableFuture
. However I do not need any specific behaviour from it so I just mocked it away.
[...] returns mockk() {
every { addCallback(any(), any()) } returns Unit
}
This is the part where I mock the ListenableFuture
.
All of that I had to do, to simply just use verify
and assert that my dispatchEvent
function is calling the PubSubTemplate#$publish
function with the expected topic
string.
I did a quick google research to see what I can do better.
Unit Test Iteration #1
The first thing I was wondering about was, if there is a MockK idiomatic way to mock a function that returns Unit
.
And I found this sweet article/documentation:
https://notwoods.github.io/mockk-guidebook/docs/mockito-migrate/void/
So instead of using returns Unit
one can simply replace every
with justRun
.
The statement now looks a bit slicker
every { pubSubTemplate.publish(eq(testTopic), any<GameEvent>()) } returns mockk() {
justRun { addCallback(any(), any()) }
}
But wait! There is more!
Unit Test Iteration #2
So still I am unhappy with verify
being dependant on the every
mock of pubSubTemplate
. I then stumbled over https://mockk.io/#relaxed-mock
I had no idea what "relaxed" might imply in the context of unit testing. So I took a little research. It says "allows creation with no specific behaviour". So all functions are returning "Simple Values" by default - so me as a developer doesn't need to implement a behaviour with every
for the called functions of that unit under test.
The only thing I needed to do was to call the mockk()
function with relaxed = true
argument.
internal class EventDispatcherTest {
private val pubSubTemplate: PubSubTemplate = mockk(relaxed = true)
private val eventDispatcher: EventDispatcher = EventDispatcher(pubSubTemplate)
@Test
fun `should publish game event message`() {
val testTopic = "projects/barbarus-game/topics/task"
eventDispatcher.dispatchEvent()
verify { pubSubTemplate.publish(eq(testTopic), any<GameEvent>()) }
}
}
And voila the simple verify
assertion does it job without the need of a behaviour definition of the mocked class.
Final Words
I hope I could help one or two developers with this. I'd wish I knew it earlier but hey - better now than never eh?
Top comments (0)