Mocking SharedPreferences.Editor.putString() - android

I recently started coding my really first android project using Android Studio 3.1.2 and SDK 19.
I'm writing unit tests for my components at the moment and use Mockito for mocking android API dependant objects. When I wrote the test for my SessionHandler, a helper class, that manages data stored in SharedPreferences I came across the problem, that, if I want to check, if e. g. mockEdit.remove("my_key") was successful, I didn't know, how to mock the behavior in particular.
This is how I prepare my mocking stuff:
private final Context mockedContext = Mockito.mock(Context.class);
private final SharedPreferences mockedPrefs = Mockito.mock(SharedPreferences.class);
private final SharedPreferences.Editor mockEdit = Mockito.mock(SharedPreferences.Editor.class);
private boolean shouldReturnTestUUID = true;
#Before
public void prepareMocks() {
Mockito.when(mockedContext.getSharedPreferences(anyString(), anyInt()).thenReturn(mockedPrefs);
Mockito.when(mockedPrefs.getString("my_key", null)).thenReturn(shouldReturnTestUUID ? "test_UUID" : null);
//this is the one, I got stuck at
Mockito.when(mockEdit.remove("my_key")).thenReturn(mockEdit.putString("my_key", null));
}
The method i'm actually testing:
public synchronized static void removeAppInstanceID(Context context) {
if (appInstanceID != null) {
SharedPreferences sharedPrefs = context.getSharedPreferences("session", Context.MODE_PRIVATE);
sharedPrefs.edit().remove("my_key").apply();
}
}
The test method:
#Test
public void canRemoveUUID() {
shouldReturnTestUUID = false;
SessionHandler.removeAppInstanceID(mockedContext);
assertNull(mockedPreferences.getString("my_key", null));
shouldReturnTestUUID = true;
}
If I try to run this test I get an UnfinishedStubbingException referring to the line where I want to mock mockEdit.remove("my_key"). Seemingly, the stub doesn't know what to do with mockEdit.putString("my_key", null);.
So my question is, how to mock this method, so I can call mockedPrefs.getString("my_key") and check if the returned value is null? Thanks in forward.

You have two options:
Mock SharedPreferences using Robolectric like here:
https://stackoverflow.com/a/9755286/1150795. Robolectric is a common
tool for unit testing Android applications and Mocking objects from
Android SDK
You can add additional layer of abstraction and hide
saving SharedPreferences behind an interface, which can be mocked with Mockito

Maybe a unit test (without integration with Android Framework) would be enough. You could only test that the remove() and apply() methods are called.
For that you need to determine the editor (a mock in this case) returned by edit() method
A Kotlin example, using Mockito, below...
#Test
public void canRemoveUUID() {
// Arrange
val mockEdit = mock(SharedPreferences.Editor::class.java)
`when`(mockEdit.remove("my_key"))
.thenReturn(mockEdit)
val mockedPrefs = mock(SharedPreferences::class.java)
`when`(mockedPrefs.edit())
.thenReturn(mockEdit)
val mockedContext = mock(Context::class.java)
`when`(mockedContext.getSharedPreferences("session", Context.MODE_PRIVATE))
.thenReturn(mockedPrefs)
// Act
SessionHandler.removeAppInstanceID(mockedContext);
// Assert
verify(mockEdit, times(1)).remove("my_key")
verify(mockEdit, times(1)).commit()
}

Related

Patterns.EMAIL_ADDRESS returns null

#RunWith(MockitoJUnitRunner.Silent.class)
public class LoginActivityTest {
#InjectMocks
LoginActivity loginActivity;
private Pattern emailPattern;
#Before
public void createLogin(){
this.emailPattern = Patterns.EMAIL_ADDRESS;
}
#Test
public void checkValidation(){
mock(LoginActivity.class);
UserVO userVO = new UserVO();
userVO.setEmailID("invalid");
userVO.setPassword("a");
boolean b = loginActivity.validatesFields(userVO);
assertFalse(b);
}
}
this.emailPattern = Patterns.EMAIL_ADDRESS; This is creating null pointer object in MockitoJunitTestClass. But, when I run this on Activity it gets initialized properly.
Use PatternsCompat instead of Patterns
I was having a similar problem because it was just a simple test, but when I added #RunWith(AndroidJUnit4::class) the problem was fixed. Check if this is test that must run with Android resources or not.
I am a little confuse with your test:
You are mocking LoginActivity.class but not setting anything with that. I believe you want to do something like loginActivity = mock(LoginActivity.class); instead.
Also, your are mocking instead spying the class, so it won't access the real method in order to verify the flow of this method. In other words, your test is doing nothing in fact.
Finally, this emailPattern is never used on your test (probably it is used on you code), so I believe you want to mock it (I am supposing it). What I recommend you do is something like this:
#RunWith(MockitoJUnitRunner.Silent.class)
public class LoginActivityTest {
#Spy
#InjectMocks
private LoginActivity loginActivity;
#Mock
private OtherStuff otherStuff;
#Test
public void checkValidation(){
UserVO userVO = new UserVO();
userVO.setEmailID("invalid");
userVO.setPassword("a");
doReturn(Patterns.EMAIL_ADDRESS).when(otherStuff).doStuff();
boolean result = loginActivity.validatesFields(userVO);
assertFalse(result);
}
}
What I did here is just an example of unit test which is validating what validateFields() is doing. I suppose that inside this method you have some method on, what I name otherStuff, which calls a method that returns Patterns.EMAIL_ADDRESS, which is what you want to mock.
It would be really better if you insert the LoginActivity code to be more precise here, but I hope I helped you.

What is the purpose of #Rule in Android Espresso UI Test?

New to Android Unit Testing with Espresso, under #Rule, what is the purpose of creating a member variable? Does the name of the variable matter? I get the inkling that I need to tell the test unit which activity (or service, class) I'm testing, but is the variable and its scope used anywhere I need to care about?
#Rule
public ActivityTestRule<MenuActivity> mActivityTestRule = new ActivityTestRule<>(MenuActivity.class);
After doing some more practice and reserach with Android UI testing with Espresso, got many of the use cases for the #Rule variables. Of of which is the with testing Idling Resources (View and data that would happen async). Using the ActivityTestRule object (ex. mActivityTestRule) I can reference resources, fire public methods with tag #VisibleForTesting in that class.
ex.
// In the activity
#VisibleForTesting
#NonNull
public SimpleIdlingResource getmSimpleIdlingResource()
{
if (mSimpleIdlingResource == null)
{
mSimpleIdlingResource = new SimpleIdlingResource();
}
return mSimpleIdlingResource;
}
// In the Test class
// the test is run.
#Before
public void registerIdlingResource() {
mIdlingResource = mActivityTestRule.getActivity().getmSimpleIdlingResource();
}

Android :cannot unit test android functions receiving NPE

I have just started unit testing in android. I cannot unit test android internal method to notify that data is changed (notifyDataSetChanged)and I am getting null pointer exception on this point. I am using mockito and power moockito to mock different objects.
public void RecyclerView(String value,Bitmap image) {
RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainActivity.this);
recyclerView.setAdapter(adapter);// set adapter on recyclerview
adapter.notifyDataSetChanged();// Notify the adapter
}
My unit test is:
#RunWith(PowerMockRunner.class)
#PrepareForTest({MainActivity.class})
public class MainActivityTest {
#Mock
Bitmap bitmap;
#Mock
RecyclerView recycleview;
#InjectMocks
Activity activity;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mainactivity = new MainActivity();
}
#Test
public void PopulateRecyleViewTest() {
try {
final RecyclerViewAdapter abc = PowerMockito.mock(RecyclerViewAdapter.class);
PowerMockito.whenNew(RecyclerViewAdapter.class).withArguments(mainactivity).thenReturn(abc);
doNothing().when(abc).notifyDataSetChanged(); //do nothing getting exception here
mainactivity.recyclerView = recycleview;
mainactivity.PopulateRecyleView("", bitmap);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Is there any way to unit test this method so that notifydatasetchanged() do not give NPE(Null Pointer Exception)? I have read that power mockito is used to unit test final method but it does not seem to be unit testing notifyDataSetChanged which is final method. Any help would be appreciated.
Acc to Android documentation - "If your unit test has no dependencies or only has simple dependencies on Android, you should run your test on a local development machine"
Mockito/PowerMockito runs on your JVM.
#InjectMocks
Activity activity;
It is not straight forward to mock the activity that is why we have Instrumented Unit Tests. I see lot of problem's in the above test case. I think you should write instrumentation case in your case.
Use Mockito/PowerMockito where you have more of Java objects, like your presentation class (in MVP architecture) or any business logic.
Please checkout android documentation on testing here.
This will give you proper picture of how and what should be your approach for writing unit test cases in Android.
With Mockito 2.0 and above you can mock final classes and methods by adding a file. Check out this document: Mock the unmockable: opt-in mocking of final classes/methods
Basically it states that you have to create the file src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker then add the single line:
mock-maker-inline
After that you should be able to mock a final class or method. I had the same issue you had and this worked for me.

How to assertion a void method in Robolectric Unit Test

How to verify a void method call in Robolectric test case where as no data coming out the called method.
What to assert in this case? Below given an example of the requirement.
public class SampleClass(){
final String TAG = SampleClass.class.getSimpleName();
public void log(){
Log.d(TAG, "Entry Loggd");
}
}
#Test
public void logEntry_test(){
SampleClass sc = new SampleClass();
sc.log();
// What to assert here to verify this log method
}
First off, good on you for writing tests!!! There are a few ways to go about testing that an internal logger is called. It's equally as important to understand what you're looking to test. Testing that a class is logging a specific message is most likely a fragile test, so be fore-warned that you probably don't need it.
Method #1: Using Robolectric
Robolectic documentation doesn't lend itself to answering basic questions, but its codebase is very well documented with its tests. A basic understanding of its principles and how shadows work can get you a long way. ShadowLog tests lay the ground work to this solution.
#RunWith(RobolectricTestRunner.class)
public class SampleClassTest {
#Test
public void log_writesExpectedMessage() {
new SampleClass().log();
ShadowLog.LogItem lastLog = ShadowLog.getLogs().get(0);
assertThat(lastLog.msg).isEqualTo("some message");
// or
assertThat(lastLog.msg).isNotNull();
}
}
Tests using Robolectric v3.1.2
Add the following to your build.gradle file:
testCompile 'org.robolectric:robolectric:3.1.2'
Method #2: Making use of Abstractions
If your sample class derives from an Android class (Activity, Fragment, Application, etc), then using android.util.Log makes sense, but bear in mind that your test will need to be a Robolectric or AndroidInstrumented test. If your SampleClass is just some POJO, then using a simple logging framework may make your testing efforts easier. For example, using Jake Wharton's Timber, your class and test can be written as follows:
import timber.log.Timber;
public class SampleClass {
void log() {
Timber.d("some message");
}
}
// SampleClassTest.java
public class SampleClassTest {
// setting up a Tree instance that we define below
TestTree testTree = new TestTree();
#Test
public void log_writesExpectedMessage() {
// setting up Timber to us the test classes log writer
Timber.plant(testTree);
// invoke the logging function
new SampleClass().log();
// assert
assertThat(testTree.lastMessage).isEqualTo("some message");
}
private class TestTree extends Timber.Tree {
private String lastMessage;
#Override
protected void log(int priority, String tag, String message, Throwable t) {
lastMessage = message;
}
}
}
Good luck, happy testing!
In my understanding you want to mock static methods. I guess, using static mocks are not the most elegant way to testing. Better to use an abstraction as recommended by abest. Although, it can be done with PowerMock.

Confused how to use Mockito for an android test

I'm trying to write a unit test for my android app but having trouble doing what I want with mockito. This is being used in conjunction with Robolectric which I have working just fine and have demonstrated that unit tests work.
I want to test whether or not a button will open a new activity depending on whether there is some bluetooth device connected. Obviously, there is no device connected with bluetooth in my test, however I want to pretend as though there is. The state of the bluetooth connection is stored in my Application class. There is no publicly accessible method to change this value.
So basically the logic in the app is like this:
HomeActivity.java:
//this gets called when the button to open the list is clicked.
public void openListActivity(View button) {
MyApplication myApplication = (MyApplication) getApplication();
if (myApplication.isDeviceConnected() {
startActivity(new intent(this, ListActivity.class));
}
}
So to test this I did the following:
TestHomeActivity.java:
#Test
public void buttonShouldOpenListIfConnected() {
FlexApplication mockedApp = Mockito.mock(MyApplication.class);
Mockito.when(mockedApp.isDeviceConnected()).thenReturn(true);
//listViewButton was setup in #Before
listViewButton.performClick();
ShadowActivity shadowActivity = Robolectric.shadowOf(activity);
Intent intent = shadowActivity.getNextStartedActivity();
assertNotNull(intent); //this fails because no new activity was opened. I debugged this and found that isDeviceConnected returned false.
ShadowIntent shadowIntent = Robolectric.shadowOf(intent);
assertThat(shadowIntent.getComponent().getClassName(), equalTo(ListActivity.class.getName()));
}
So my unit test fails because the call (in the activity) to isDeviceConnected returns false even though I thought I told it to return true with the mock framework. I want my test to have this method return true though. Isn't this what mockito does or am I totally mistaken on how to use mockito?
That's how mockito works, but the problem is: is your listViewButton using your mockedApp? Seems not, because you're creating mockedApp at the test method and never setting it anywhere. Mockito will not mock the method calls of all instances of Application, only from what you declared as a mock.
I personally don't know how android works with the Application class, but you will have to set it somewhere so listView use your mockedApp instead of what it receives normally.
EDIT
After the updated question, you can transform your getApplication in a protected method, spy you listViewButton and make it return your mockedApp. That smells a little bad, but it's one way if you can not set your application mocked object to listViewButton.
EDIT2
Example of using spy in your test using BDDMockito for readability :)
public HomeActivity {
...
protected MyApplication getApplication() {
// real code
}
...
}
public void TestHomeActivity {
private HomeActivity homeActivity;
#Before
public void setUp() {
this.homeActivity = spy(new HomeActivity());
}
#Test
public void buttonShouldOpenListIfConnected() {
// given
FlexApplication mockedApp = Mockito.mock(MyApplication.class);
Mockito.when(mockedApp.isDeviceConnected()).thenReturn(true);
// IMPORTANT PART
given(homeActivity.getApplication()).willReturn(mockedApp);
...
}
}
After that, your test should work as expected. But I reinforce: Use spy only if you can't inject your mockedApp inside HomeActivity.
Your mocked version isn't being called.
See that call, getApplication()? (below). That's returning a real copy of your MyApplication class, not your mocked version. You'd need to intercept the getApplication() call and pass in your mocked Application object.
HomeActivity.java:
//this gets called when the button to open the list is clicked.
public void openListActivity(View button) {
MyApplication myApplication = (MyApplication) getApplication(); // returns the real thing
if (myApplication.isDeviceConnected() {
startActivity(new intent(this, ListActivity.class));
}
}
I'm not sure this is possible with Mockito. Have you tried customizing the ShadowActivity#getApplication() method?

Categories

Resources