Mocking kotlin property with accessors in Mockito - android

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?

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.

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

Related

How to conditionally skip a unit test in Android and kotlin?

I need to run a unit test based on whether this asset exists at runtime. The reason is, I am downloading the file in react native and in android, I am running some unit tests that requires this file.
I would like to run the unit test only if this file exists. Does anyone have any suggestions or code samples on how this can be achieved? Or is there another way these unit tests can be accomplished?
You shouldn't do that in a unit test because you want to have the file locally in your testing environment or have a mock that provides it. Otherwise you're not Eradicating Non-Determinism in Tests.
Let's assume you need to do something like that anyway. So, I would add a condition in the assert expression:
#Test
fun `Given a variable When has a value Then assert it has a value`(){
var myVar = null
myVar = getAValue()
myVar?.let {
assertNotEquals(null, myVar)
}
}
To me, eradicating non determinism in this particular context means that test scope has always specific expected values, the conditional expression in the let or an if enclosing an assert expression violates that. Hence the code above shouldn't be part of your tests. Instead, you have to write a test for the case myVar is null, you write another test for the case myVar is non null.
That's why I use Given, When, Then the conditional state would make the Given/When very messy.

Unit test a helper class around SharedPreference

I have a helper class to save user object to shared preferences. I have used a serialize(): String function and a create(serializedString: String) function in my User data model. They use GSon serializer and are working good as suggested by the unit tests on them.
Now my helper class is called SharedPreferenceUserStore.kt which takes a Context object. The code is:
class SharedPreferenceUserStore(context: Context) {
companion object {
val TAG = SharedPreferenceUserStore::class.java.simpleName
}
var userLocalSharedPref: SharedPreferences =
context.getSharedPreferences(USER_LOCAL_STORE_SHARED_PREF_NAME, Context.MODE_PRIVATE)
/*
Store the required data to shared preference
*/
#SuppressLint("ApplySharedPref")
fun storeUserData(user: User) {
val userLocalDatabaseEditor = userLocalSharedPref.edit()
val serializedData = user.serialize()
userLocalDatabaseEditor.putString(
USER_LOCAL_STORE_SHARED_PREF_SERIALIZED_DATA_KEY,
serializedData
)
if (userLocalDatabaseEditor.commit()) {
Log.d(TAG, " Store Commit return true")
}
}
/*
Clear all the locally stored data from the shared pref
*/
#SuppressLint("ApplySharedPref")
fun clearUserData() {
val userLocalDatabaseEditor = userLocalSharedPref.edit()
userLocalDatabaseEditor.clear()
userLocalDatabaseEditor.commit()
}
fun getLoggedInUser(): User? {
val stringUser = userLocalSharedPref.getString(
USER_LOCAL_STORE_SHARED_PREF_SERIALIZED_DATA_KEY, "")
return if (stringUser==null || stringUser == ""){
null
} else{
User.create(stringUser)
}
}
And I have written some unit tests for this helper class as follows:
#RunWith(JUnit4::class)
class SharedPreferenceUserStoreTest {
lateinit var sharedPreferenceUserStore: SharedPreferenceUserStore
lateinit var user: User
//to be mocked
lateinit var sharedPreferences: SharedPreferences
lateinit var sharedPreferencesEditor: SharedPreferences.Editor
lateinit var context: Context
#Before
fun setUp() {
//mocking Context and SharedPreferences class
context = mock(Context::class.java)
sharedPreferences = mock(SharedPreferences::class.java)
sharedPreferencesEditor = mock(SharedPreferences.Editor::class.java)
//specifying that the context.getSharedPreferences() method call should return the mocked sharedpref
`when`<SharedPreferences>(context.getSharedPreferences(anyString(), anyInt()))
.thenReturn(sharedPreferences)
//specifying that the sharedPreferences.edit() method call should return the mocked sharedpref editor
`when`(sharedPreferences.edit()).thenReturn(sharedPreferencesEditor)
//specifying that the sharedPreferencesEditor.putString() method call should return the mocked sharedpref Editor
`when`(sharedPreferencesEditor.putString(anyString(), anyString())).thenReturn(
sharedPreferencesEditor
)
`when`(sharedPreferences.getString(anyString(), anyString())).thenReturn("")
//instantiating SharedPreferenceUserStore from the mocked context
sharedPreferenceUserStore = SharedPreferenceUserStore(context)
user = User(
35,
"Prashanna Bhandary",
"prashanna.bhandary#gmail.com",
"dd58a617ea618010c2052cb54079ad67.jpeg",
"98********",
"test address 01",
1,
"yes",
"2019-08-30 04:56:43",
"2019-08-30 05:14:47",
0
)
}
#After
fun tearDown() {
}
#Test
fun passUser_storeUserData() {
sharedPreferenceUserStore.storeUserData(user)
verify(sharedPreferencesEditor).putString(
Constants.USER_LOCAL_STORE_SHARED_PREF_SERIALIZED_DATA_KEY,
user.serialize()
)
verify(sharedPreferencesEditor).commit()
}
#Test
fun testClearUserData() {
sharedPreferenceUserStore.clearUserData()
verify(sharedPreferencesEditor).clear()
}
#Test
fun testGetLoggedInUser_storeNotCalled() {
//calling getLoggedInUser() without calling storeUserData() should give null
assertEquals(null, sharedPreferenceUserStore.getLoggedInUser())
//verify that getString() was called on the shared preferences
verify(sharedPreferences).getString(Constants.USER_LOCAL_STORE_SHARED_PREF_SERIALIZED_DATA_KEY, "")
}
#Test
fun testGetLoggedInUser_storeCalled(){
//call getLoggedInUser(), we are expecting null
assertNull(sharedPreferenceUserStore.getLoggedInUser())
//verify that getString() was called on the shared preferences
verify(sharedPreferences).getString(Constants.USER_LOCAL_STORE_SHARED_PREF_SERIALIZED_DATA_KEY, "")
}
}
As I am really new to Unit Testing and Mocking libraries like Mockito. Now my question is are my tests any good? and I wanted to test if the getLoggedInUser() funciton of my helper class is doing what it is supposed to do (ie. get logged in user if shared pref has it), how do I do that?
In addition do suggest me any improvements I can make to my test or the helper class itself. Thank you.
Judging your test for what it is - A unit test running on a host machine with Android dependencies mocked with Mockito - it looks fine and like what you would expect.
The benefit-to-effort ratio of such tests are debatable, though. Personally I think it would be more valuable to run such a test against the real SharedPreferences implementation on a device, and assert on actual side effects instead of verifying on mocks. This has a couple of benefits over mocked tests:
You don't have to re-implement SharedPreferences with mocking
You know that SharedPreferenceUserStore will work with the real SharedPreferences implementation
But, such tests also have a debatable benefit-to-effort ratio. For a solo developer project, think about what kind of testing that is most important. Your time is limited so you will only have time to spend on writing the most important kind of tests.
The most important kinds of tests are the ones that test your app in the same way your users will use it. In other words, write high-level UI Automator tests. You can write how many mocked or on-device unit tests as you want. If you don't test that your entire app as a whole works, you will not know that it works. And if you don't know that your app as a whole works, you can't ship it. So in some way you have to test your app in its entirety. Doing it manually quickly becomes very labour intensive as you add more and more functionality. The only way to continually test your app is to automate the high-level UI testing of your app. That way you will also get code coverage that matters.
One big benefit of high-level UI testing that is worth pointing out is that you don't have to change them whenever you change some implementation detail in your app. If you have lots of mocked unit tests, you will have to spend a lot of time to refactor your unit tests as you refactor the real app code, which can be very time consuming, and thus a bad idea if you are a solo developer. Your UI Automator tests do not depend on low-level implementation details and will thus remain the same even if you change implementation details.
For example, maybe in the future you want to use Room from Android Jetpack to store your user data instead of SharedPreference. You will be able to do that without changing your high level UI tests at all. And they will be a great way to regression test such a change. If all you have are mocked unit tests, it will be a lot of work to rewrite all relevant unit tests to work with Room instead.
I agree with what #Enselic say about favoring Integration Test over Unit Tests.
However I disagree with his statement that this mockito test looks fine.
The reason for that is that (almost) every line in your code under test involves a mock operation. Basically mocking the complete method would have the same result.
What you are doing in your test is testing that mockito works as expected, which is something you should not need to test.
On the other hand your test is a complete mirror of the implementation itself. Which means everytime you refactor something, you have to touch the test. Preferably would be a black box test.
If you use Mockito you should try to restrict its use to methods that actually do something (that is not mocked).
Classes that generally should be mocked for testing purposes are dependencies that interact with external components (like a database or a webservice), however in these cases you are normally required to have Integration Tests as well.
And if your Integration-Tests already cover most part of the code, you can check whether you want to add a test using a mock for those parts that are not covered.
I have no official source for what I am trying to express, its just based on my experience (and therefore my own opinion). Treat it as such.
There is not much that can be said regarding the tests that guys before me haven't said.
However, one thing that you might want to consider is refactoring your SharedPreferenceUserStore to accept not Context(which is quite a huge thing, and if not handled properly could lead to unforeseen issues and/or memory leaks), but rather SharedPreferences themselves. This way, your class, that deals only with updating the prefs doesn't have access to more than it should.

Android Unit Testing with Mockito

I have a ViewModel in which there is a method which has the following line of code:
billDate.set(!TextUtils.isEmpty(SampleApp.getInstance().getAccountManager().getDueDate()) ?
String.format(SampleApp.getInstance().getApplicationContext().getString(R.string.due),
SampleApp.getInstance().getAccountManager().getBillingDueDate()) :
SampleApp.getInstance().getApplicationContext().getString(R.string.missing_due_date));
I have a test class using Mockito to test the different methods in ViewModel. But it is failing with NullPointerException at this line:
String.format(SampleApp.getInstance().getApplicationContext().getString(R.string.due),
Below is the log:
java.lang.NullPointerException
at java.util.regex.Matcher.getTextLength(Matcher.java:1283)
at java.util.regex.Matcher.reset(Matcher.java:309)
at java.util.regex.Matcher.<init>(Matcher.java:229)
at java.util.regex.Pattern.matcher(Pattern.java:1093)
at java.util.Formatter.parse(Formatter.java:2547)
at java.util.Formatter.format(Formatter.java:2501)
at java.util.Formatter.format(Formatter.java:2455)
at java.lang.String.format(String.java:2940)
While running a test case, I see the log showing some error related to Pattern
Can somebody suggest, how to test the String.format() method?
First of all, you should not be importing android view packages into your ViewModel. So skip using things like TextUtils inside ViewModels.
As to the getApplicationContext().getString(), create an interface for this. Something like:
interface StringProvider {
String getString(int resource);
}
Then pass that interface in your ViewModel constructor and use that to get the string you want.
When you initialize the ViewModel, you can pass a concrete implementation of StringProvider like this:
class StringProviderImpl implements StringProvider {
String getString(int resource) {
return SampleApp.getInstance().getApplicationContext().getString(resource);
}
}
This way, for your unit tests, you can just mock StringProvider and don't have to worry about dealing with contexts inside your ViewModel and the related test code.
You don't need to test the String.format method. That is not your code, and your goal should be to test your own code. But your code is using that method, so you need to test your code. This is the part you are trying to validate or mock out as I understand it:
String.format(SampleApp.getInstance().getApplicationContext().getString(R.string.due), SampleApp.getInstance().getAccountManager().getBillingDueDate())
which makes several calls to SampleApp to get an instance. Since those calls to SampleApp.getInstance are static method calls, you won't be able to mock them out. There isn't enough code posted to know what SampleApp is or what SampleApp.getInstance() returns or to know if any of the subsequent calls on that instance are returning null, but one of them is. So I think to solve this you need to look at the what the getInstance method returns. If you can't touch that code and you're hoping to only modify your test classes, you may not be able to test this with mockito due to the static method.
But otherwise you will need to build a way for your tests so the call to SampleApp.getInstance returns a mock object as the instance instead of whatever real instance I presume it is returning now. Then you can mock out the subsequent methods like getApplicationContext and getString to make them return canned responses so that the string.format call will not fail on a null input.
One note of caution--if you do end up making the static getInstance method return a mock, but sure you have proper cleanup when your test is done to set it back to what it was returning originally so you don't inadvertently modify something that might cause another unrelated unit test to fail. That is always a risk if you change something returned by a static method in a unit test since you are effectively changing it for all tests.
Considering that the test fails after the AccountManager was already used, you should have set up the SampleApp as a mock or fake already.
SampleApp app = SampleApp.getInstance()
AccountManager am = app.getAccountManager();
Context context = app.getApplicationContext();
billDate.set(!TextUtils.isEmpty(am.getDueDate()) ?
String.format(context.getString(R.string.due), am.getBillingDueDate()) :
context.getString(R.string.missing_due_date);
Now you only need to make sure to mock the Context you provide with with app.getApplicationContext() or the SampleApp itself, if you use app.getString() directly.
doReturn(dueFormatString).when(context).getString(R.string.due);
doReturn(dueMissingString).when(context).getString(R.string.missing_due_date);
But in general you should abstract the Context away. Not using it will simplify your code and therefore your testing a lot.
Also consider using context.getString() instead of String.format() for formatting a string you load from a resource. It's as easy as adding the format arguments as parameters to the call.
context.getString(R.string.due, am.getBillingDueDate())

How much of a mock is a Mockito mock?

This is a serious question, I promise. I've spent the last 2 hours reading as many definitions of Mock that I could find and none explain this to me.
I've got a class I want to test and that class requires a mapper class as part of it's primary constructor:
open class PoiListViewModel #Inject constructor(
private val mapper: PoiMapper
) : ViewModel() {
In my unit test I have the following code:
//Mock objects needed to instantiate the class under test
#Mock lateinit var mapper: PoiMapper
// Class being tested
lateinit var poiListViewModel: PoiListViewModel
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
poiListViewModel = PoiListViewModel(mapper)
}
My question to you all smart developers is, what exactly is a mock? And specifically how much of my original class does it replicate?
I'll tell you my assumed definition. A mock is a fake stand-in class that stands in for my real class, but that it does nothing except keep track of what method calls get sent to it. If I want the mock to have any functionality I need to stub that functionality in.
At least that's my ignorant view of mocks. But I'm apparently wront because in my unit test, my "mock" mapper class seems to be an actual mapper class. If I debug my unit test I see it walk through all the code of my mapper class. I see it returning converted data.
Here's the mapper class code (if it matters):
open class PoiMapper #Inject constructor() {
fun mockTest(num: Int): Int{
return num *23
}
fun mapToPresentation(domainModel: Poi_Domain): Poi_Presentation {
var test = 3
var results = mockTest(test)
return Poi_Presentation(domainModel.id,domainModel.name,domainModel.description,
domainModel.img_url,domainModel.latitude,domainModel.longitude,domainModel.imgFocalpointX,
domainModel.imgFocalpointY,domainModel.collection,domainModel.collectionPosition,
domainModel.release,domainModel.stampText)
}
}
Can someone explain it to me, how much of a mock is a Mockito mock? Did I instantiate the mocks incorrectly? Can someone give me a better way to think of mocks so I can wrap my head around all this?
Your understanding of mocks is correct. You're bumping into Kotlin's final-by-default behavior, as an implementation detail of Mockito.
Mockito mocks (as distinct from Mockito spies) are defined to take the place of your class instance. Unless you've stubbed them to return otherwise, they record all interactions and return default dummy values (zero, an empty string, an empty list, null, etc). You can confirm that they collaborated correctly with your system-under-test by stubbing return values (when/thenReturn), stubbing specific behaviors for methods (when/thenAnswer), or by checking that certain methods were called (verify) and retrieving the specific instances they were called with (ArgumentCaptor).
Under the hood, Mockito achieves this by generating a subclass of the class you're mocking, and overriding all methods to delegate to Mockito's internal handler. This is what gives Mockito the power to override the behavior silently: Your system-under-test thinks it's calling your dependency, but your code is using Java's virtual method dispatch to override your dependency's behavior.
Here's the trick: Java methods are virtual by default, unless you mark them final. In Kotlin, functions are closed by default, unless you mark them open. (I'm going to keep calling them final, because that's the definition at play in the JVM or Android Dexer, which is reading the bytecode that Kotlin generates anyway.) When the virtual machine is sure of a reference's type based on static typing, and you're calling a final method, the compiler is allowed to inline that implementation's code (JLS 8.4.3.3) because you've asserted that the method cannot be overridden and any code that tries to override it will fail at compilation. Mockito's generated code isn't compiled this way, and Mockito can't detect this case.
In your case, mapToPresentation is not open, so the compiler sees it as final and does not keep the virtual method dispatch that would let Mockito override the behavior. Your definition of mocking is right, and your code would be right, except that Kotlin is closed-by-default where Java is open-by-default. Besides, you really do rely on the function being open, because you'd like it to be called with a different implementation than the one in the function.
Trivially, you could just make all functions open that you intend to override, or use Mockito 2.1+'s built-in feature to mock final methods. However, as a general best practice you want an object that behaves like a PoiMapper even if it doesn't follow your specific PoiMapper implementation. That might be a good reason for an interface/impl split, such that your class PoiListViewModel takes a PoiMapper interface. In production you can provide a PoiMapperImpl as you have, but Mockito can generate an arbitrary implementation of the PoiMapper interface without worrying about whether PoiMapperImpl and its functions are open or closed.
Have you added the annotation
#RunWith(MockitoJUnitRunner.class)
to your test class?

Using multiple TestRules in the same test

I have written a custom TestRule to use with my Android test suite. It populates a table in the database used by the app under test. Now I need to use this DataRule along with ActivityTestRule. Can I have two fields of my test class annotated with #Rule? How do I control the order in which the rules are applied?
Background:
The Android API provides a TestRule for starting an Activity which is the core class for every app with a UI. My app has a database and I have several tests which require the database to be pre-populated with some known data. Previously, with JUnit3-based tests, I used an abstract superclass to centralize the code which prepares the database and then I extended this superclass for different test cases. Now I am trying to implement the same logic using JUnit 4. I learned recently that test rules are one way to provide logic which is reused across tests, so I am trying to move the logic from my superclass to a test rule. Is this an appropriate way to achieve my goal?
You certainly can have multiple #Rule fields in a single test. I'm not sure what the default ordering of rule application is, or if it's even well-defined. However, if ordering is important you can control it with a RuleChain
which allows you to define an order on how rules are applied when you have multiple rules in a test case.
From the Javadoc...
#Rule
public RuleChain chain = RuleChain
.outerRule(new LoggingRule("outer rule")
.around(new LoggingRule("middle rule")
.around(new LoggingRule("inner rule");
RuleChain is deprecated and since 4.13 you can make use of order parameter in Rule.
org.junit.Rule annotation has a parameter "order" which you can use to order the Rules in one file.
check the doc in the link below
Rule.java
If you're using JUnit for your tests, which I personally recommend, it's not recommended to have multiple rules in the same file, because a Rule is a unit of your test, and as you're doing unit tests, you should have just one Rule per file.
If you need to create some sort of data before you run your tests you should use the #Before and then load the necessary information.
More on this can be found here: http://junit.sourceforge.net/javadoc/org/junit/Before.html
If you have to load the same data in multiple classes, I would recommend you to create a class with your logic, extend that class in your test class and then create a method annotated with #Before an call your super class method.
Hope that helps

Categories

Resources