Testing with Threads

pablisco
4 min readFeb 20, 2018

Recently, I came across a component that relied on threads. I wanted to test it’s behaviour. However, soon I came across the issues that come with parallelisation (always have to look up how to spell that word).

val scheduler = Scheduler()
var result: String? = null

scheduler.schedule {
result = "expected value"
}

assertThat(result, equalTo("expected value"))

The problem with this code is that it will always reach the assertion before it has a chance to execute the code provided to the scheduler. Hence why result remains with a null value at the assert stage.

Back in Java days, one way to solve this would have used wait() and notify() in order to wait for the value to be set. However, these aren’t exposed in Kotlin. They can be exposed by casting any value to Object. However, it significantly increments the cognitive complexity of the code. Specially given that we require the use of several synchronised {} blocks.

The easiest way to avoid the problem is using a BlockingQueue like SynchronousQueue.

val scheduler = Scheduler()
val queue = SynchronousQueue<String>()
scheduler.schedule {
queue.put("expected value")
}
assertThat(queue.take(), equalTo("expected value"))

This works because the thread running our test is blocked by the take() function from the queue. Once the asynchronous code is executed, it will put a value in the queue which in turn notifies the blocked thread where the test are running.

Delegation

Using queues the code looks OK. However, since we are using Kotlin we can make things nicer. We can encapsulate the queue inside of a delegated property.

class FutureValue<A> {
private val queue by lazy { SynchronousQueue<A>() }
operator fun getValue(
thisRef: Nothing?,
property: KProperty<*>
): A = queue.take()
operator fun setValue(
thisRef: Nothing?,
property: KProperty<*>,
value: A
) = queue.put(value)
}

fun <A> future() = FutureValue<A>()

The two public operators match the ones in ReadWriteProperty. Kotlin will see that and allow to use this type as a delegated property. In simple terms these method will be called any time the property is set or retrieved.

Our intuition may tell us that delegated properties are used just for member properties. If only, by their name. However, in Kotlin local values are functionally properties of the current scope. So, they can be used in a test without having to define a member property.

val scheduler = Scheduler()
var result by future<String>()
scheduler.schedule {
result = EXPECTED_VALUE
}
assertEquals(result, EXPECTED_VALUE)

This removes the boilerplate of using a queue directly. Making the actual test code more readable.

Coroutines

When writing this I thought about using coroutines and channels.

However, similarly to wait/notify they require to be run with a context. It can be done, but adds complexity.

runBlocking {
val scheduler = Scheduler()
val channel = Channel<String>()
scheduler.schedule {
launch { channel.send(EXPECTED_VALUE) }
}
assertEquals(channel.receive(), EXPECTED_VALUE)
}

That said, if we put it in a delegated property we don’t need all the boilerplate either.

class AsyncValue<A> {
private val channel by lazy { Channel<A>() }
operator fun getValue(
thisRef: Nothing?,
property: KProperty<*>
): A = runBlocking { channel.receive() }
operator fun setValue(
thisRef: Nothing?,
property: KProperty<*>,
value: A
) = launch { channel.send(value) }
}

fun <A> async() = AsyncValue<A>()

And it can be used exactly in the way as with the blocking queue delegate.

var result by async<String>()

Interestingly, I’ve observed some delay when using coroutines. I’ll try to run some benchmarks for a future article.

Times on the different methods of suspension

Back to basics

I mentioned the use of the old fashioned wait/notify combo before. And even though on its own is a bit complicated, once hidden away in a delegated property, it looks exactly the same as the others ones.

It doesn’t look to have a huge improvement in performance compared to the use of a queue. Furthermore, using a queue is slightly more elegant since it doesn’t rely on an optional and writable property.

Show me what you got!

The code I produced for this article can be found here:

https://gist.github.com/pablisco/6d4d5c3c132091a78a64ece885383b93

--

--