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.

Ben P.
  • 52,661
  • 6
  • 95
  • 123
  • How do you know it's null? Does it cause a crash? – TheWanderer Jan 10 '19 at 20:14
  • 1
    And, do you literally mean "unit test" (`app/src/test/`) and not "instrumented test" (`app/src/androidTest/`)? – CommonsWare Jan 10 '19 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 – Ben P. Jan 10 '19 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. – Ben P. Jan 10 '19 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. – CommonsWare Jan 10 '19 at 20:19
  • Possible duplicate of [Intent extras in Unit test Android Mockito](https://stackoverflow.com/questions/46469049/intent-extras-in-unit-test-android-mockito) – TheWanderer Jan 10 '19 at 20:21
  • The question isn't exactly the same, but it looks like @CommonsWare has the right idea: if you do a unit test with Android classes, methods are no-op and any that have an Object return will return null. – TheWanderer Jan 10 '19 at 20:23
  • Yep, that sounds totally plausible. Testing now... – Ben P. Jan 10 '19 at 20:24
  • Confirmed. Thanks very much for the help, all! – Ben P. Jan 10 '19 at 20:55

1 Answers1

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.

Ben P.
  • 52,661
  • 6
  • 95
  • 123