Search code examples
androidjunitandroid-testingjunit3

junit in android - deleting a test changes the results of a different test


I just right click on the project and run in as Junit Test with the android framework - the project has 3 files

Base class

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager;
import android.test.AndroidTestCase;

public class AccessPreferencesTest extends AndroidTestCase {

    static Context ctx;
    static SharedPreferences prefs;
    Editor e;
    static final boolean DEFAULT_BOOLEAN = true;
    @Override
    protected void setUp() throws Exception {
        super.setUp();
        ctx = getContext();
        prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
        e = prefs.edit();
    }
}

The file that throws...

public final class AccessPreferencesNullValuesTest extends
        AccessPreferencesTest {

    public void testNullBollean() {
        prefs.getString("BOOLEAN_KEY", "DEFAULT_STRING");
    }
}

...if I delete the testPutNullBoolean() from this file

import gr.uoa.di.android.helpers.AccessPreferences;

public final class AccessPreferencesBooleanTest extends AccessPreferencesTest {

    public void testPutNullBoolean() {
        AccessPreferences.put(ctx, "BOOLEAN_KEY", null);
        Boolean b = AccessPreferences.get(ctx, "BOOLEAN_KEY", null);
        assertEquals(null, b);
    }

    public void testPutBoolean() {
        AccessPreferences.put(ctx, "BOOLEAN_KEY", DEFAULT_BOOLEAN);
        boolean b = AccessPreferences.get(ctx, "BOOLEAN_KEY", null);
        assertEquals(DEFAULT_BOOLEAN, b);
    }
}

The gr.uoa.di.android.helpers.AccessPreferences (totally alpha so don't shoot)

My launcher

Needless to say it took all day to reduce it to those 3 files.

So if I have testPutNullBoolean :

enter image description here

while if I delete it :

enter image description here

where the "failure trace" reads :

java.lang.ClassCastException: java.lang.Boolean
at android.app.ContextImpl$SharedPreferencesImpl.getString(ContextImpl.java:2699)
at gr.uoa.di.android.helpers.test.AccessPreferencesNullValuesTest.testNullBollean(
    AccessPreferencesNullValuesTest.java:7)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
at android.test.InstrumentationTestRunner.onStart(
    InstrumentationTestRunner.java:520)
at android.app.Instrumentation$InstrumentationThread.run(
    Instrumentation.java:1447)

I do not mind the CCE (again this is a much slimed down version of the tests + a WIP) - what I do not get is why the tests are correlated. New to testing so maybe there is a glaring bug in there but I am literally dizzy to see it now :)


Solution

  • Well, I was a noob :

    public final class AccessPreferences {
    
        private static SharedPreferences prefs;
    
        private static SharedPreferences getPrefs(Context ctx) {
            SharedPreferences result = prefs;
            if (result == null)
                synchronized (AccessPreferences.class) {
                    result = prefs;
                    if (result == null) {
                        result = prefs = PreferenceManager
                            .getDefaultSharedPreferences(ctx);
                    }
                }
            return result;
        }
    
        public static <T> void put(final Context ctx, final String key,
                final T value) {
            final Editor ed = getPrefs(ctx).edit();
            if (value == null) {
                ed.putString(key, null); // this was called in testPutNullBoolean()
                // **nulling** the boolean I put into prefs with testPutBoolean()
                //  - which run earlier (they run alphabetically (?) - not in the 
                // order they are declared anyway). So prefs.getString() found
                // null and did not throw - whereas if I deleted testPutNullBoolean()
                // it found a Boolean...
            }
            else if (value instanceof Boolean) ed.putBoolean(key, (Boolean) value); 
            //...
            ed.commit();
        }
        //...
    }
    

    So what I was missing was that at my setUp() the same ctx was returned :

    //AccessPreferencesTest
    
    static Context ctx;
    
    @Override
    protected void setUp() throws Exception {
        super.setUp();
        ctx = getContext(); // here
        // ...
    }
    

    So just setting prefs to null was not enough - I had to explicitly clear them.

    //AccessPreferencesTest
    
    static SharedPreferences prefs; Editor e; // set up in setUp()
    
    @Override
    protected void setUp() throws Exception {
        super.setUp();
        ctx = getContext();
        prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
        e = prefs.edit();
        }
    
    @Override
    protected void tearDown() throws Exception {
        // Field f = AccessPreferences.class.getDeclaredField("prefs");
        // f.setAccessible(true);
        // // f.set(null, null); // NO USE
        // final SharedPreferences sp = (SharedPreferences) f.get(null);
        // if (sp != null) sp.edit().clear().commit();
        // I only have to clear the preferences via an editor - the
        // SharedPreferences are a singleton in the context of a single Context
        // so no need to access them via AccessPreferences and no need to
        // nullify the field in AccessPreferences [ie call f.set(null, null)] -
        // as the reference to the singleton stays valid - apparently
        if (ed != null) ed.clear().commit();
        super.tearDown();
    }
    

    Would help if getContext() stated clearly that the same ctx is returned - unfortunately there are no docs at all !