A case with Kotlin let

One of my test cases were failing. It was to verify that a particular function is getting called when data is empty. Instead it was getting called twice which was unexpected.

The code I was testing had a let block and Elvis operator. If a value is non null then let block is executed. Else the expression to the right of Elvis operator gets executed.

Kotlin’s let function helps executing code within its block scope by taking the object on which it is invoked as parameter. let can be ideally used on nullable objects to execute something only if they are non-null.

Below is the code on which the test case was written.

kotlin-let

The test case was to check whether view.showEmptyState() was getting called when validData was empty. But it was failing.

The value from getValidData() was actually empty. So according to the code it was supposed to pass. I was totally confused.

But after some debugging I found that view.showEmptyState() was getting called twice.

But I have view.showEmptyState() only at two places. One inside let block and other to the right of elvis operator.

Surprised I set out to find what was going wrong.

First I went to Kotlin doc for let:

Screen Shot 2019-03-09 at 1.50.37 PM
Source: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/let.html

Take a look at the last few words of above sentence. It says let returns a result. That caught my eyes. I again went to the code and found the IDE hint for the return statement.

So last line of the let is implied as the return statement.

In my code the last line is analytics?.triggerEvent(validData) . If you notice analytics is a nullable type. Since it is not mocked or tested, it is always null!

As analytics is null, the let block returns null. This returned value is checked against the elvis operator. And since it is null it goes to the else part. So view.showEmptyState() was getting called again after let block is executed.

So that’s why the test case was failing.

Ideally we should have mocked the analytics to pass the test. But during runtime if analytics somehow was null, then the case will fail.

So the solution would be to have a non-null value as the return type, or simply a Unit. Another option is to go with good old if-else instead.

Lesson learnt is that sometime we don’t care about the semantics of the code we are using. But if we’re careless there might be cases where the code won’t work as intended is what we should understand.

Anyway, I should thank the IDE for hinting me about the semantic of let.