4

I'm trying to write a unit test for some Android code that looks for a specific key being present in an Intent object. As part of my test, I'm creating an Intent object and adding a String to it. However, when I use the below code, my variable is initalized to null:

val data = Intent().putExtra("key", "value")
// data is null

If I split this up into two lines, it works just fine:

val data = Intent()
data.putExtra("key", "value")
// data is non-null and contains my key/value

What feature of the Kotlin language is causing this to happen?


Note that putExtra() returns an Intent object. From the Android source:

public @NonNull Intent putExtra(String name, String value) {
    if (mExtras == null) {
        mExtras = new Bundle();
    }
    mExtras.putString(name, value);
    return this;
}

In the first case, the inferred type is Intent!. I was under the impression that this just means that it's an Intent or an Intent? but Kotlin doesn't want to make devs go crazy with Java platform types. Still, given that putExtra() returns a non-null value, I'd expect the actual value of data to be non-null.

9
  • How do you know it's null? Does it cause a crash? Commented Jan 10, 2019 at 20:14
  • 1
    And, do you literally mean "unit test" (app/src/test/) and not "instrumented test" (app/src/androidTest/)? Commented Jan 10, 2019 at 20:15
  • @TheWanderer I know it is null due to both stepping through the code in the debugger and due to an NPE in my code under test Commented Jan 10, 2019 at 20:15
  • @CommonsWare it is in the test/ directory, not androidTest/, but if I use the two-line approach the test runs successfully and passes. Commented Jan 10, 2019 at 20:16
  • 3
    Unit tests run on the JVM. There is no Intent class in the JDK. So, you are getting your Intent class from someplace else (e.g., Mockito). The rules of what putExtra() returns might be different for the mock than it is for the real Intent class. Commented Jan 10, 2019 at 20:19

1 Answer 1

1

The short answer is what @CommonsWare and @TheWanderer mentioned in comments: my test class was in the test/ directory, so it was using a mock Intent implementation instead of the real thing.

When I move my test to the androidTest/ directory, everything works as expected. The observed behavior has nothing to do with Kotlin.


Some extra info about why this was so confusing...

First, I was mistaken when I wrote this:

val data = Intent()
data.putExtra("key", "value")
// data is non-null and contains my key/value

The data variable was non-null, but it did not actually contain my key/value pair. The mock Intent implementation I was using was dropping the putExtra() call.

So, why was my test passing?

The one particular test I decided to dig deeper on was testing the negative case (when a key other than the one it expects is present in the Intent). But I wasn't passing an Intent with the wrong key, I was passing an Intent with no keys at all. Either way, though, the expected key is not present, and the method returns false.

The positive case (where the required key actually was passed to putExtra()) failed with an AssertionError. Too bad I didn't pick this one to scrutinze.

My main project has apparently stubbed Intent.putExtra() as a no-op, via the returnDefaultValues = true gradle option. When I create a new project and try to reproduce this issue, I get a very clear error:

java.lang.RuntimeException: Method putExtra in android.content.Intent not mocked. See http://g.co/androidstudio/not-mocked for details.
    at android.content.Intent.putExtra(Intent.java)
    at com.example.stackoverflow.IntentTest.test(IntentTest.kt:12)

Unfortunately, with the mocked putExtra(), I never got this helpful message.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.