Trying to mock SharedPreferences using Mockito - android

I'm trying to use Mockito to test a settings manager which saves data through SharedPreferences.
Since SharedPreferences makes use of Context, I need to use mock classes.
This is my settings manager class:
public class SettingsManager implements ISettingsManager {
protected SharedPreferences prefs;
public SettingsManager(SharedPreferences prefs) {
this.prefs = prefs;
}
private boolean getBooleanPreference(String key) {
return prefs.getBoolean(key, true);
}
private void setBooleanPreference(boolean enabled, String key) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(key, enabled);
editor.commit();
}
}
This is the test case I wrote:
Context mContext = Mockito.mock(Context.class);
SharedPreferences mSharedPreference = Mockito.mock(SharedPreferences.class);
SharedPreferences.Editor mEditor = Mockito.mock(SharedPreferences.Editor.class, Mockito.RETURNS_DEEP_STUBS);
Mockito.when(mSharedPreference.edit()).thenReturn(mEditor);
Mockito.when(mEditor.commit()).thenReturn(true);
Mockito.when(mEditor.putBoolean(Mockito.anyString(), Mockito.anyBoolean())).thenReturn(mEditor);
SettingsManager manager = new SettingsManager(mSharedPreference);
boolean current = manager.areNotificationsEnabled();
manager.setNotificationsEnabled(!current);
boolean newValue = manager.areNotificationsEnabled();
Assert.assertTrue(newValue != current);
The problem is when I set the setNotificationsEnabled flag, the newValue remains the same of current: SharedPreferences does not persist data. How can I save data to SharedPreferences while testing?

Robolectric is an option for this kind of integration test.
Robolectric provides test doubles called "shadows" of common Android classes like Context, SQLiteDatabase and SharedPreferences. The tests you write run in test in your IDE (not androidTest on an emulator or test device) so it is easier to configure tools for test coverage.
The shadow SharedPreference is sandboxed as well so it won't interfere with the actual SharedPreferences on a device.

Convert this to an AndroidTest and use InstrumentationRegistry.getTargetContext() to get a context, this way you can use the class without mocking it

Related

Android instrument test wipes the shared preference data while loading native library

When I create Android Instrumentation test case below, I find a strange issue. With the line of loading native lib System.loadLibrary("jnidispatch");, the test case is failed; without that line the test case is working fine. I test with any native libraries, the issue is same, so it is not because of the native library behavior to wipe out the shared preference. It seems a bug of android instrumentation test, any one can explain why?
#RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
private static final String MY_ID = "MY_ID";
private static final String TEST_DATA = "Test";
#Test
public void sharedPreferenceTest() {
Context context = InstrumentationRegistry.getContext();
// Set ID
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(MY_ID, TEST_DATA);
editor.apply();
System.loadLibrary("jnidispatch"); // load native libary
// Get ID
SharedPreferences sharedPref1 = PreferenceManager.getDefaultSharedPreferences(context);
String data = sharedPref1.getString(MY_ID, null);
Assert.assertEquals(data, TEST_DATA);
}
}
The sample project can be downloaded here.
UPDATE: In the end I found out it's the device issue, the issue only happens on certain device, like OPPO device.

How to access sharedpreference value in non-Activity Java class?

I want to get Shared preference values in non-Activity Url Constants class where I need to check url which will come from a previous Activity. I am using common Shared Preference Utility class where I am using Shared Preference Manager to put and get values through shared preferences; however whenever I try to access shared preference value in Url constants class, I cannot access the common shared preference utility class. How can I get the value ? Please help.
My Shared Preference class is:
public class Preference {
private static final String PREFIX = "json";
public static void setString(String key, String value, Context context) {
SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(key, value);
editor.apply();
}
public static String getString(String key, Context context) {
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(context);
return preferences.getString(key, null);
}
You should pass context, instead doing any hacks with context
Ideally, we should get this done once and access it anywhere. What I mean is we can create a single instance of Sharedpreference when starting Application class and make any calls on this object.
public class AppController extends Application {
static AppController appController;
public static AppController getInstance(){
return appController;
}
#Override
public void onCreate() {
super.onCreate();
Fabric.with(this, new Crashlytics());
appController = this;
}
}
get it named in manifest first before use in application tag with name attribute.
<application
android:name=".AppController"
......
......
</application>
So we can initialize here our SharedPreference or get application instance like this. We can also use Dagger for this and do much more.
AppController.getInstance().getApplicationContext().getSharedPreferences("userdetails", MODE_PRIVATE);
OR
we can just pass context to some constructor of a non-actvitiy/fragment class.
AS show your code you pass context that's not problem but the problem with your class name Preference it should be similar to java inbuilt class like java.android.preference and java.util.pref so make sure you are pointing your project class not android java default calss. or you may try to change your util class with other

Android - right way to store a value for repeated use?

I have a service in my app that is always running but the global static variables seem to get reset when the phone is idle for a while (possibly the app is getting closed). Please let me know the optimal way to store a value for repeated use, maybe once in 2-5 mins.
Will using a SharedPreference cause high overhead if accessed once in 2-5 mins ?
Appreciate your help.
SharedPreference is best option.
public class AppPreference {
public static final String APP_NAME_KEY= "your_app_name";
public static final String SAMPLE_KEY = "sample";
public SharedPreferences preferences;
private SharedPreferences.Editor editor;
private String sample;
public AppPreference(Context context) {
preferences = context.getSharedPreferences(APP_NAME_KEY, Context.MODE_PRIVATE);
editor = preferences.edit();
}
public void setSample(String sample) {
this.sample= sample;
editor.putString(SAMPLE_KEY , this.sample);
editor.commit();
}
public String getSample() {
return preferences.getString(SAMPLE_KEY, null);
}
}
You can use Integer, Float, boolean values according to your requirement.

is SharedPreferences access time consuming?

I'm currently trying to test a third party service for my app, and need to identify each test that is being done at every specific run.
Since more than one test can take place every time I run the testApp, I need to Identify every test.
What I thought of, is storing the device name and build (not many devices here), and an index for each test.
private String getTestId(){
SharedPreferences settings = getPreferences(0);
SharedPreferences.Editor editor = settings.edit();
int testNumber = settings.getInt("id", 0);
editor.putInt("id", testNumber+1);
editor.commit();
String id = Build.DEVICE + Build.VERSION.RELEASE+" - test number: "+testNumber;
return id;
}
Is running this function every time I run a test time consuming, or can I do this without fearing the coast?
if the answer is "time consuming", what would you suggest I do every time I run a test in order to differentiate every test?
About SharedPreferences.
SharedPreferences caches after first load, so disk access to load data will take time but once. You can try to load SharedPreferences early in your test suite to avoid this penalty.
For persisting your data you should opt for SharedPreferences.Editor.apply() instead of SharedPreferences.Editor.commit() since appy is asynchronous. But please do read the documentation about both to see which one applies in your case.
The question already has an answer, but in case others come and are looking for a code sample, I put together this utility class for interacting with the SharedPreferences.
Calling commit() will use the apply() method if it's available, otherwise it will default back to commit() on older devices:
public class PreferencesUtil {
SharedPreferences prefs;
SharedPreferences.Editor prefsEditor;
private Context mAppContext;
private static PreferencesUtil sInstance;
private boolean mUseApply;
//Set to private
private PreferencesUtil(Context context) {
mAppContext = context.getApplicationContext();
prefs = PreferenceManager.getDefaultSharedPreferences(mAppContext);
prefsEditor = prefs.edit();
//Indicator whether or not the apply() method is available in the current API Version
mUseApply = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD;
}
public static PreferencesUtil getInstance(Context context) {
if (sInstance == null) {
sInstance = new PreferencesUtil(context);
}
return sInstance;
}
public boolean getBoolean(String key, boolean defValue) {
return prefs.getBoolean(key, defValue);
}
public int getInt(String key, int defValue) {
return prefs.getInt(key, defValue);
}
public String getString(String key, String defValue) {
return prefs.getString(key, defValue);
}
public String getString(String key) {
return prefs.getString(key, "");
}
public void putBoolean(String key, boolean value) {
prefsEditor.putBoolean(key, value);
}
public void putInt(String key, int value) {
prefsEditor.putInt(key, value);
}
public void putString(String key, String value) {
prefsEditor.putString(key, value);
}
/**
* Sincle API Level 9, apply() has been provided for asynchronous operations.
* If not available, fallback to the synchronous commit()
*/
public void commit() {
if (mUseApply)
//Since API Level 9, apply() is provided for asynchronous operations
prefsEditor.apply();
else
//Fallback to syncrhonous if not available
prefsEditor.commit();
}
}
I've noticed that when you use methods like putInt() the first time for a specific key it can take a significant amount of time. Besides, it should be equivalent to any other ways of writing to a file.

Initialization of static variables in a class of utility functions

For my Android application, I have written a class which is composed of utility functions which are needed at various activites in the application.In this class, I need a context variable(for working with files) and an instance of preference manager and preference editor.Also, a long integer represnting the current date as a timestamp is needed:
private static long today;
private static Context myContext;
private static SharedPreferences sharedPrefs;
private static Editor editor;
Which is correct way to initialize these variables. I have tried doing it via a private constructor as shown below, but I am getting errrors.
private NetworkController()
{
//Getting the Unix timestamp for today
GregorianCalendar aDate = new GregorianCalendar();
GregorianCalendar tDate = new
GregorianCalendar(aDate.get(Calendar.YEAR),aDate.get(Calendar.MONTH),
aDate.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
today = (tDate.getTimeInMillis())/1000;
//The preferences manager for reading in the preferences
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(myContext);
//The preferences editor for modifying the preferences values
editor = sharedPrefs.edit();
}
One approach would be to create an instance of this class in every activity where its used but I don,t want to do that.Any other approach is possible?
If you have a set of things that you use everywhere and only want one instance of, you can use what's called a singleton. For example, here is a very simple one that holds an integer called level:
public class Utility {
private static Utility theInstance;
public int level;
private Utility() {
level = 1;
}
public static getUtility() {
if (theInstance == null) {
theInstance = new Utility();
}
return theInstance;
}
}
Then you can use this like:
Utility u = Utility.getUtility();
u.level++;
However, many people discourage the use of singletons, since they can lead to confusing program behaviour. A good article on this topic is Singletons are Pathological Liars. Singletons can be useful in some situations, but you should be aware of the traps involved in using them.
#Greg is right, just don't use any static stuff for what you want to do. There is no reason you don't want to have normal objects here. Pass the context as parameter and instanciate you objects when you need them to serve you :
private long today;
private Context myContext;
private SharedPreferences sharedPrefs;
private Editor editor;
public NetworkController( Context context )
{
this.context = context;
//Getting the Unix timestamp for today
GregorianCalendar aDate = new GregorianCalendar();
GregorianCalendar tDate = new
GregorianCalendar(aDate.get(Calendar.YEAR),aDate.get(Calendar.MONTH),
aDate.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
today = (tDate.getTimeInMillis())/1000;
//The preferences manager for reading in the preferences
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this.context);
//The preferences editor for modifying the preferences values
editor = sharedPrefs.edit();
}
Singletons are a bad way of programming things, it makes things very hard to test. Even you don't yet use tests, don't use singletons, there lead to very poor quality code and real ball of muds when things get more complicated.
Here you can do this:
public class NetworkController {
SharedPreferences settings;
SharedPreferences.Editor editor;
public NetworkController(Context context){
settings = PreferenceManager.getDefaultSharedPreferences(context);
editor = settings.edit();
}
public void saveName(String name){
editor.putString("name", name).commit();
}
public String getName(){
return settings.getString("name");
}
public static long getTimeStamp(){
return System.currentTimeMillis();
}
}
You can use the class like below:
NetworkController prefs = new NetworkController(context); // Context being an Activity or Application
prefs.saveName("blundell");
System.out.println(prefs.getName()); // Prints 'blundell';
System.out.println(NetworkController.getTimeStamp()); // Prints 1294931209000
If you don't want to create an instance in every class you could create on instance in your Application and always reference that:
public class MyApplication extends Application {
private NetworkController myPrefs;
public NetworkController getPrefs(){
if(myPrefs == null){ // This is called lazy initialization
myPrefs = new NetworkController(this); // This uses the Application as the context, so you don't have issues when Activitys are closed or destroyed
}
return myPrefs;
}
}
You need to add the MyApplication to your manifest:
<application
android:name="com.your.package.MyApplication"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name">
To use this single instance you would do this:
public class MyActivity extends Activity {
public void onCreate(Bundle savedInstanceState){
super(savedInstanceState);
NetworkController prefs = ((NetworkController) getApplicationContext()).getPrefs();
// use this object just like shown above
prefs.saveName("blundell"); // etc
}
}
There's already a bunch of good suggestions posted here, but I suppose another approach for these kind of 'utility'/'helper' functions is to simply pass in the parameters you need the logic to work on. In your case, in stead of trying to make the logic work on a local Context reference, you could simply pass it in:
public static void NetworkController(Context context) {
//Getting the Unix timestamp for today
GregorianCalendar aDate = new GregorianCalendar();
GregorianCalendar tDate = new
GregorianCalendar(aDate.get(Calendar.YEAR),aDate.get(Calendar.MONTH),
aDate.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
long today = (tDate.getTimeInMillis())/1000;
//The preferences editor for modifying the preferences values
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
...
}
The other variables you can calculate/deduce on the fly. It'll probably mean a bit more garbage collection, but should be relatively safe in terms of memory management.

Categories

Resources