Test SharedPreferences in Android - android

I am coming into a legacy codebase which uses SharedPreferences for data persistence. I'd like to test saving/retrieving a value, mocking using MockK. However, the assertion in this unit test never passes. It is as if the SharedPrefs aren't stored properly in testing:
class MyProfilePrefsTest {
private lateinit var myProfilePrefs: ProfilePrefs
#RelaxedMockK private lateinit var mockSharedPrefs: SharedPreferences
#RelaxedMockK private lateinit var context: Context
#Before
fun setup() {
MockKAnnotations.init(this)
val sharedPreferences = mockk<SharedPreferences>()
every { sharedPreferences.edit() } returns (mockk())
myProfilePrefs = ProfilePrefs(context, sharedPreferences)
mockStatic(DeviceInfo::class)
every { DeviceInfo.serialNumber() } returns "fake_serial"
}
#Test
fun `Saving correct cellular download pref for device id`() {
// Arrange
val isEnabled = true
// Act
myProfilePrefs.setCellularDownloadingEnabled(isEnabled)
// Assert
assertTrue(myProfilePrefs.getCellularDownloadingEnabled())
}}
Anybody know how to unit test SharedPrefs?

You need Robolectric library to test classes related to the Context. This library will simulate an Android device(without emulator).
In that case, you can use RuntimeEnvironment.application.getApplicationContext() which will return real, not mocked object of Context class.
update as of May 2020:
RuntimeEnvironment.application.getApplicationContext() is now Deprecated.
please use ApplicationProvider.getApplicationContext() to get the Context. also, keep in mind that you should add testImplementation 'androidx.test:core:1.2.0' to your build.gradle.
so
Espresso can help you as well, but it is instrumental tests though.

Thanks to #samaromku's suggested answer above. Here is the full solution. It uses AndroidX Test runner:
#RunWith(AndroidJUnit4::class)
class ProfilePrefsTest {
private lateinit var profilePrefs: ProfilePrefs
private lateinit var context: Context
#Before
fun setup() {
context = getApplicationContext<MyApplication>()
val sharedPreferences = context.getSharedPreferences(
"prefs",
MODE_PRIVATE
);
profilePrefs = ProfilePrefs(context, sharedPreferences)
mockStatic(DeviceInfo::class)
every { DeviceInfo.serialNumber() } returns FAKE_SERIAL_NUMBER
}
#Test
fun `Saving correct cellular download pref for device id`() {
// Arrange
val isEnabled = true
// Act
profilePrefs.setCellularDownloadingEnabled(isEnabled)
// Assert
assertTrue(profilePrefs.isCellularDownloadingEnabled())
}
}

Related

Kotlin mocking a var with get/set using Mockk

I have this Kotlin class that is a wrapper around SharedPreferences in an Android app.
class Preferences(private val context: Context) {
private val preferences: SharedPreferences =
context.getSharedPreferences("name_of_file", Context.MODE_PRIVATE)
// Integers
var coins: Int
get() = preferences.getInt(KEY_COINS, 0)
set(value) = preferences.edit { putInt(KEY_COINS, value) }
var pressure: Int
get() = preferences.getInt(KEY_PRESSURE, DEFAULT_PRESSURE)
set(value) = preferences.edit { putInt(KEY_PRESSURE, value) }
}
I need to mock this class in order to be able to use it in some unit tests for my viewmodels. I tried mocking the get/set methods of the properties but for some reason I'm getting some errors and I need a bit of help.
This is how I try to mock the Preferences class:
private val sharedPreferences = mutableMapOf<String, Any>()
...
val preferences = mockk<Preferences>()
listOf("coins", "pressure").forEach { key ->
every { preferences getProperty key } returns sharedPreferences[key]
every { preferences setProperty key } answers { // exception on this line
sharedPreferences[key] = fieldValue
fieldValue
}
}
And I get this exception when running any of the tests that involves this mock
io.mockk.MockKException: Missing mocked calls inside every { ... } block: make sure the object inside the block is a mock
I think the error is quite cryptic. Or, is there any way to mock these fields using mockk?
I have also read the examples from here where I got the inspiration for this solution but seems like there is something I'm missing.

Android MVVM testing with Mockito, mock repository.observeValue() has problem

I'm trying to test MVVM with Mockito.
The architecture of MVVM is similar to Android architecture blueprints.
We observe data from repository as LiveData.
And try to test observed value like below code.
class SplashViewModel(
private val appRepository: AppRepository
) {
val appInfo: LiveData<AppInfo> = appRepository.observeAppInfo()
}
#Test
fun getAppInfo() {
`when`(appRepository.observeAppInfo())
.thenReturn(appInfoData)
assertEquals(appInfoData, viewModel.appInfo.getOrAwaitValue())
}
The crucial point is viewModel.appInfo returns null, despite of I used mockito.
The problem
The creation of ViewModel is faster than using mockito.
So appInfo property is initialized with null, cause it dosen't know what observeAppInfo() is.
First solution
At first, I just trying to solve this problem with custom getter. Like this.
class SplashViewModel(
private val appRepository: AppRepository
) {
val appInfo: LiveData<AppInfo> get() = appRepository.observeAppInfo()
}
Now every time I access to appInfo they just re evaluate the data.
But is has it's own problem.
In this situation appVersion is getting error.
val appVersion: LiveData<String> = appInfo.map {
...
}
So every transformation LiveData(Like Transformations, MediatorLiveData) must use custom getter too.
And I felt it's not a great solution.
How do you think of it?
You could use #BeforeClass to make sure your initialisation is run before the tests.
#BeforeClass
fun setup(){
appInfo = appRepository.observeAppInfo()
}
See more

Unable to access value from `Transformations.map` in unit test

To give some background to this question, I have a ViewModel that waits for some data, posts it to a MutableLiveData, and then exposes all the values through some properties. Here's a short gist of what that looks like:
class QuestionViewModel {
private val state = MutableLiveData<QuestionState>()
private val currentQuestion: Question?
get() = (state.value as? QuestionState.Loaded)?.question
val questionTitle: String
get() = currentQuestion?.title.orEmpty()
...
}
Then, in my test, I mock the data and just run an assertEquals check:
assertEquals("TestTitle", viewModel.questionTitle)
All of this works fine so far, but I actually want my fragment to observe for when the current question changes. So, I tried changing it around to use Transformations.map:
class QuestionViewModel {
private val state = MutableLiveData<QuestionState>()
private val currentQuestion: LiveData<Question> = Transformations.map(state) {
(it as? QuestionState.Loaded)?.question
}
val questionTitle: String
get() = currentQuestion.value?.title.orEmpty()
...
}
Suddenly, all of my assertions in the test class have failed. I made currentQuestion public and verified that it's value is null in my unit test. I've determined this is the issue because:
I can mock the data and still get the right value from my state LiveData
I can run my app and see the expected data on the screen, so this issue is specific to my unit test.
I have already added the InstantTaskExecutorRule to my unit test, but maybe that doesn't handle the Transformations methods?
I recently had the same problem, I've solved it by adding a mocked observer to the LiveData:
#Mock
private lateinit var observer: Observer<Question>
init {
initMocks(this)
}
fun `test using mocked observer`() {
viewModel.currentQuestion.observeForever(observer)
// ***************** Access currentQuestion.value here *****************
viewModel.questionTitle.removeObserver(observer)
}
fun `test using empty observer`() {
viewModel.currentQuestion.observeForever {}
// ***************** Access currentQuestion.value here *****************
}
Not sure how it works exactly or the consequences of not removing the empty observer the after test.
Also, make sure to import the right Observer class. If you're using AndroidX:
import androidx.lifecycle.Observer
Luciano is correct, it's because the LiveData is not being observed. Here is a Kotlin utility class to help with this.
class LiveDataObserver<T>(private val liveData: LiveData<T>): Closeable {
private val observer: Observer<T> = mock()
init {
liveData.observeForever(observer)
}
override fun close() {
liveData.removeObserver(observer)
}
}
// to use:
LiveDataObserver(unit.someLiveData).use {
assertFalse(unit.someLiveData.value!!)
}
Looks like you're missing the .value on the it variable.
private val currentQuestion: LiveData<Question> = Transformations.map(state) {
(it.value as? QuestionState.Loaded)?.question
}

JUnit test not sending values as parameters to function (Kotlin)

I am creating a simple junit test to test a function in my view model but the first assertion fails as the function I call returns null. When I debug the function I call has null parameters which is weird cause I pass them in.
I have spent time debugging and searching for why I am having that issue but I have found nothing that fixes my issue or tells me what the issue is.
#RunWith(MockitoJUnitRunner::class)
class CurrencyUnitTest {
#Rule
#JvmField
val rule = InstantTaskExecutorRule()
#Mock
val currencyViewModel : CurrencyViewModel = mock(CurrencyViewModel::class.java)
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
val rates: HashMap<String, Double> =
hashMapOf(
"USD" to 1.323234,
"GBP" to 2.392394,
"AUD" to 0.328429,
"KWR" to 893.4833
)
val currencyRates = MutableLiveData<Resource<CurrencyRatesData?>>()
val resource = Resource<CurrencyRatesData?>(Status.SUCCESS, CurrencyRatesData("CAD", rates, 0))
currencyRates.value = resource
`when`(currencyViewModel.currencyRatesData).thenReturn(currencyRates)
val baseCurrency = MutableLiveData<String>()
baseCurrency.value = "CAD"
`when`(currencyViewModel.baseCurrency).thenReturn(baseCurrency)
}
#Test
fun calculateValueTest() {
// this fails
assertEquals("0.36", currencyViewModel.calculateValue("AUD", "1.11"))
}
}
Mocked classes will not really be called. If you want to test your currencyViewModel.calculateValue() method, create a real object of that class and mock possible constructor arguments.
To add to what Ben has said: the class you want to test has to be a real object, not a mock. A mock "does nothing" per default, and only does what you do it to tell you, so to test it does not make any sense.
What you mock is the dependencies of the class you test, i.e. the objects you pass to its' constructor.
In short: if you want to test CurrencyViewModel, create an object of it instead of mocking it.

How to mock Android Lifecycle components?

I am trying to build an Android app following the recommended design structure.
Let's say, there is a UserRepository for handling the users. However, I would like to have certain settings in the app, for example "Show profile picture", "Sort by", etc. I would like to store these settings in a Room database, just like the Users.
According to my understanding, the cleanest way is to have a separate UserRepository and a SettingsRepository. And of course, the Settings should have a sort of Model, let's call it SettingsModel, in order to be able to retrieve the Settings as a Map for example. Note, that this is not a ViewModel, it has nothing to do with the UI.
Then, the UserRepository should implement its own business (handling the users), just like in the example linked above. Besides that, it also should have a dependency of that SettingsModel, so that it can easily retrieve the settings, which affect how the Users should be retrieved.
The SettingsModel needs to turn the "raw database data" into a map, so that I can reach the settings like this: settings.show_profile_pictures and settings.sort_by, etc.
To achieve this, I need to extract the data from the LiveData, which implies, that I need to observe that LiveData, so that I can update the Map, whenever the settings change.
And here comes the problem: the observe() method needs a LifecycleOwner, which I cannot provide in my tests.
1st attempt: mock with Mockito
It would be an instrumented test, becase that way I have access to an Activity, which is needed to retrieve the DAO.
I am trying to #Inject it with Dagger, but the mocking wasn't successful:
class SettingsRepositoryTest {
private lateinit var settingsDao: SettingsDao
#Mock
private lateinit var mockLifecycleOwner: LifecycleOwner
#Before
fun createDb(){
MockitoAnnotations.initMocks(LifecycleOwner::class.java)
val appContext = InstrumentationRegistry.getTargetContext()
val db = Room.inMemoryDatabaseBuilder(appContext, CurrencyConverterDb::class.java).allowMainThreadQueries().build()
settingsDao = db.settingsDao()
}
#Test
fun testSettingsMap() {
val repo = SettingsRepository(settingsDao, mockLifecycleOwner) // throws the exception here
}
}
The exception:
kotlin.UninitializedPropertyAccessException: lateinit property mockLifecycleOwner has not been initialized
at com.helmet91.currencyconverter.repositories.SettingsRepositoryTestInst.testSettingsMap(SettingsRepositoryTestInst.kt:46)
2nd attempt: use Roboelectric to create an AppCompatActivity, which is in fact a LifecycleOwner.
It is not an instrumented test, because Roboelectric doesn't work in the androidTest environment.
The Activity still has to be mocked, however, it throws a NullPointerException. The only way I can think of, is to go through this Exception stack, and mock everything in it, if it's even possible at all. But that sounds insane to me. There has to be a better solution.
class SettingsRepositoryTest {
private lateinit var settingsDao: SettingsDao
private lateinit var activity: AppCompatActivity
#Before
fun createDb(){
val built = Robolectric.buildActivity(MainActivity::class.java) // throws the exception here
val created = built.create()
val controller = created.start()
activity = controller.get() as AppCompatActivity
val db = Room.inMemoryDatabaseBuilder(activity, CurrencyConverterDb::class.java).allowMainThreadQueries().build()
settingsDao = db.settingsDao()
}
#Test
fun testSettingsMap() {
val repo = SettingsRepository(settingsDao, activity)
val settingsMap = repo.getSettings()
val settingsEntity = Settings(1, "show_flags", "1", "bool")
settingsDao.insert(settingsEntity)
assertTrue(settingsMap.show_flags)
}
}
The exception:
java.lang.NullPointerException
at org.robolectric.internal.bytecode.ShadowImpl.extract(ShadowImpl.java:17)
at org.robolectric.shadow.api.Shadow.extract(Shadow.java:25)
at org.robolectric.Shadows.shadowOf(Shadows.java:1215)
at org.robolectric.shadows.CoreShadowsAdapter.getMainLooper(CoreShadowsAdapter.java:23)
at org.robolectric.android.controller.ComponentController.<init>(ComponentController.java:29)
at org.robolectric.android.controller.ComponentController.<init>(ComponentController.java:21)
at org.robolectric.android.controller.ActivityController.<init>(ActivityController.java:33)
at org.robolectric.android.controller.ActivityController.of(ActivityController.java:25)
at org.robolectric.Robolectric.buildActivity(Robolectric.java:97)
at org.robolectric.Robolectric.buildActivity(Robolectric.java:93)
at com.helmet91.currencyconverter.repositories.SettingsRepositoryTest.createDb(SettingsRepositoryTest.kt:29)
Is it really impossible to test anything, that involves Lifecycle components?

Categories

Resources