Search code examples
androidunit-testingkotlinspek

How to test sharedpreference that inject in repository in Android MVP Clean Architecture


i've facing a problem to test sharedpreference in datastore. in actual datastore i implement three arguments, those include sharedpreference.

in this case i want to store value, and get that value. mocking not help here.

mocking cannot propagate actual value, that will be used by code. in second part.

class FooDataStoreTest : Spek({
given("a foo data store") {

    val schedulerRule = TestSchedulerRule()
    val service: FooRestService = mock()
    val context: Context = mock()
    val gson: Gson = mock()
    val appFooPreference: SharedPreferences = mock()

    var appFooSessionStoreService: AppFooSessionStoreService? = null
    var fooStoredLocationService: FooStoredLocationService? = null

    beforeEachTest {
        appFooSessionStoreService = AppFooSessionStoreService.Builder()
                .context(context)
                .gson(gson)
                .preference(appFooPreference)
                .build()
        fooStoredLocationService = FooStoredLocationService(appFooSessionStoreService)
    }

    val longitude = 106.803090
    val latitude = -6.244285

    on("should get foo service with request longitude $longitude and latitude $latitude") {
        it("should return success") {
            with(fooStoredLocationService) {
                val location = Location()
                location.latitude = latitude
                location.longitude = longitude

                // i want to store location in this
                fooStoredLocationService?.saveLastKnownLocation(location)

                // and retrieve in below code
                val l = fooStoredLocationService?.lastKnownLocation

                val dataStore = FooDataStore(service, preference, fooStoredLocationService!!)
                service.getFooService(longitude, longitude) willReturnJust
                        load(FooResponse::class.java, "foo_response.json")

                val testObserver = dataStore.getFooService().test()
                schedulerRule.testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
                testObserver.assertNoErrors()
                testObserver.awaitTerminalEvent()
                testObserver.assertComplete()
                testObserver.assertValue { actual ->
                    actual == load(FooResponse::class.java, "foo_response.json")
                }
            }
        }
    }

    afterEachTest {
        appFooSessionStoreService?.clear()
        fooStoredLocationService?.clear()
    }
}})

and this datastore looks like

open class FooDataStore @Inject constructor(private val fooRestService: FooRestService,
                                        private val fooPreference: FooPreference,
                                        private val fooLocation: fooStoredLocationService) : FooRepository {

private val serviceLocation by lazy {
    fooLocation.lastKnownLocation
}

override fun getFooService(): Single<FooResponse> {
    safeWith(serviceLocation, {
        return getFooLocal(it).flatMap({ (code, message, data) ->
            if (data != null) {
                Single.just(FooResponse(code, message, data))
            } else {
                restService.getFooService(it.longitude, it.latitude).compose(singleIo())
            }
        })
    })
    return Single.error(httpExceptionFactory(GPS_NOT_SATISFYING))
}

}

Actually i want to get value in from this field serviceLocation. Anyone has approach to do some test for that?, any advise very welcome.

thanks!


Solution

  • I would recommend you not to depend on SharedPreferences directly, but to have some interface LocalStorage, so you can have your SharedPrefsLocalStorage being used in the code and TestLocalStorage in the tests. SharedPrefsLocalStorage will use SharedPreferences under the hood, and TestLocalStorage some Map implementation.

    Just a simple example:

    // You may add other type, not Int only, or use the String and convert everything to String and back
    interface LocalStorage {
        fun save(key: String, value: Int)
        fun get(key: String): Int? 
    }
    
    class SharedPrefsLocalStorage(val prefs: SharedPreferences) : LocalStorage {
        override fun save(key: String, value: Int) {
            with(prefs.edit()){
                putInt(key, value)
                commit()
            }
        }
        override fun get(key: String): Int? = prefs.getInteger(key)
    }
    
    class TestLocalStorage : LocalStorage {
        val values = mutableMapOf<String, Any>()
    
        override fun save(key: String, value: Int) {
            values[key] = value
        }
    
        override fun get(key: String): Int? = map[value] as Int?
    }