Search code examples
androidunit-testingandroid-studioandroid-contextinstrumentation

Android Studio: Cannot write to Shared Preferences in instrumented test


I'm trying to write a test case to verify a class that writes to Shared Preferences. I'm using Android Studio v1.5.

In the good old eclipse, when using AndroidTestCase, a second apk file was deployed to the device, and tests could be run using the instrumentation context, so you could run tests using the instrumentation apk's shared preferences without altering the main apk's existing shared preferences files.

I've spent the entire morning trying to figure out how to get a non null context in Android Studio tests. Apparently unit tests made for eclipse are not compatible with the Android Studio testing framework, as calling getContext() returns null. I thought I've found the answer in this question: Get context of test project in Android junit test case

Things have changed over time as old versions of Android Studio didn't have full testing support. So a lot of answers are just hacks. Apparently now instead of extending InstrumentationTestCase or AndroidTestCase you should write your tests like this:

@RunWith(AndroidJUnit4.class)
public class MyTest {

    @Test
    public void testFoo(){
        Context instrumentationContext = InstrumentationRegistry.getContext();
        Context mainProjectContext = InstrumentationRegistry.getTargetContext();            
    }   
}

So I now have a non null instrumentation context, and the getSharedPreferences method returns an instance that seems to work, but actually no preferences file is being written.

If I do:

context = InstrumentationRegistry.getContext();      

Then the SharedPreferences editor writes and commits correctly and no exception is thrown. On closer inspection I can see that the editor is trying to write to this file:

data/data/<package>.test/shared_prefs/PREFS_FILE_NAME.xml

But the file is never created nor written to.

However using this:

context = InstrumentationRegistry.getTargetContext(); 

the editor works correctly and the preferences are written to this file:

/data/data/<package>/shared_prefs/PREFS_FILE_NAME.xml

The preferences are instantiated in private mode:

SharedPreferences sharedPreferences = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);

As far as I know, no test apk has been uploaded to the device after running the test. This might explain why the file was not written using the instrumentation context. Is it possible that this context is a fake context that fails silently?

And if this were the case, how could I obtain a REAL instrumentation context so that I can write preferences without altering the main project's preferences?


Solution

  • Turns out you can't write to shared preferences using the instrumentation context, and this was true even in eclipse. This would be the equivalent test for eclipse:

    import android.content.Context;
    import android.content.SharedPreferences;
    import android.test.InstrumentationTestCase;
    
    public class SharedPrefsTest extends InstrumentationTestCase {
    
        public void test() throws Exception { 
            Context context = getInstrumentation().getContext();
            String fileName = "FILE_NAME";
    
            SharedPreferences sharedPreferences = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
            SharedPreferences.Editor editor = sharedPreferences.edit();
            editor.putString("key", "value");
            editor.commit();
    
            SharedPreferences sharedPreferences2 = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
            assertEquals("value", sharedPreferences2.getString("key", null));
        }
    }
    

    I just ran it and it also fails. The preferences are never written. I think internal storage file access is forbidden in this context, as calling Context.getFilesDir() throws an InvocationTargetException, and so does calling File.exists() over the preferences file (you can check which file is the editor writing to using the debugger, just look for a private variable called mFile inside the this.$0 member instance).

    So I was wrong in thinking this was actually possible. I though we had used the instrumentation context for data access layer testing in the past, but we actually used the main context (AndroidTestCase.getContext()), although we used different names for the preferences and SQLite files. And this is why the unit tests didn't modify the regular app files.