2

I have a token property in my application class(Kotlin) that is based on a SharedPreferences value

    var token : String?
    get() = PreferenceManager.getDefaultSharedPreferences(applicationContext)
               .getString(TOKEN_PEREF_TAG, null)
    set(value) {
    PreferenceManager.getDefaultSharedPreferences(applicationContext)
            .edit()
            .putString(TOKEN_PEREF_TAG, value)
            .apply()
    }

The problem is that I can't set a mock value like this:

whenever(app.token).thenReturn("token")

since I get the error

java.lang.RuntimeException: Method getDefaultSharedPreferences in android.preference.PreferenceManager not mocked.

Shouldn't the mock just return the provided string?

how can I get around this error?

saiedmomen
  • 1,401
  • 16
  • 33

2 Answers2

4

You can fix this error by using the mockito-inline dependency instead of the mockito-core dependency. This uses a different mocking method that circumvents this issue of the platform classes not being available. It's also particularly useful because it allows you to mock final classes, therefore eliminating the need to put every one of your classes behind an interface or mark them as open in Kotlin.

This inline mocking method can also be turned on by a configuration file, however I found just using the inline dependency much more reliable.

zsmb13
  • 85,752
  • 11
  • 221
  • 226
  • I've yet to encounter any, but I'd love to see someone point them out if there are, since I've been using this. Perhaps there are performance considerations or something, but I didn't do benchmarking on it. – zsmb13 Feb 16 '18 at 20:30
  • I just realized declaring the property "token" open, would also work. But your answer is way better imo – saiedmomen Feb 16 '18 at 20:38
  • Oh, of course, that would make sense. Mockito can't mock final methods (which the getter would of course be in this case) by default. – zsmb13 Feb 16 '18 at 20:39
  • but the exception message wasn't helpful at all – saiedmomen Feb 16 '18 at 20:51
  • Yeah. It mocks the class alright when it's open, but any non-final methods don't get mocked so calls to them just call the actual implementation, and that's what crashes. It would be nice if this could be detected, as it doesn't look something you'd want to do intentionally. – zsmb13 Feb 16 '18 at 20:52
  • Strangely, using `mockito-inline` gave a runtime error for me, whenever a test is run: `java.lang.NoClassDefFoundError: android/content/Context` -- The error is described here: https://stackoverflow.com/questions/14213219/java-lang-noclassdeffounderrorandroid-and-junit-test – Mr-IDE Jul 19 '19 at 19:55
  • Make sure to get rid of the `@JvmField` annotation on the Kotlin property you are trying to mock! I thought I was going crazy because I was already using `mockito-inline` and doing all I needed to do on my test, and then I noticed that annotation that must have been put there by Android Studio's Java to Kotlin converter. I removed it and the test worked as expected. – argenkiwi Jul 01 '21 at 20:55
0

In a 'small test' (jUnit test, the one in src/test) the android framework is not present and the whole framework is just a stub and provides no functionality. You either have to create your own mocked SharedPreferences and PreferenceManager using mockito or sth similar. Or use an 'medium test' (instrumented test, the one in src/androidTest) which must run on emulator or a device. For more on this check Fundamentals of Testing

leonardkraemer
  • 6,573
  • 1
  • 31
  • 54