Search code examples
androidunit-testingrobolectricandroid-contextactiveandroid

Unit Testing ActiveAndroid Models Using Robolectric


I am using ActiveAndroid for some of my models, and I wanted to start unit testing my work. Unfortunately, I am getting a load of errors, namely in being unable to initialize ActiveAndroid using the proper context.

ActiveAndroid is iniatilized:

ActiveAndroid.initialize(context)

I have tried to initialize a context by:

  1. Have a stub class that extends Application, and use that to initialize db.

    private class TestApp extends com.activeandroid.app.Application{
       @Override
       public void onCreate() {
         super.onCreate();
         initialiseDB(getDatabaseName());
       }
    
       protected String getDatabaseName() {
         return "sad";
       }
    
       private void initialiseDB(String dbName) {
          ActiveAndroid.initialize(this); 
       }
    }
    

This fails, as the class return null for .getPackageName() and .getApplicationContext(), both of which are used internally by the initialize.

I have also tried using ShadowContextWrapper, but I may be using it wrong. Here is how I went about it:

    ShadowContextWrapper shadowContextWrapper = new ShadowContextWrapper();
    shadowContextWrapper.setApplicationName("appName");
    shadowContextWrapper.setPackageName("package");
    Context context = shadowContextWrapper.getApplicationContext();

This approach fails with an NPE at ShadowContextWrapper.java:52 which part of Robolectric. The line itself:

    Context applicationContext = this.realContextWrapper.getBaseContext().getApplicationContext();

I am using AS 1.2, robolectric3.0 and activeandroid 3.1.

Here is an example of a Test I am running.

@RunWith(CustomRobolectricTestRunner.class)
   public class ItemTest {

     public void setUp(){

     }

    @Test
    public void checkJUnitWork() {
       assertThat(true, is(true));
    }

    @Test
    public void testSave(){
       Item item = new Item("name", "units", 5.0, 4.5, 10.0);
       assertThat(item.getName(),is("name"));
    }

   public void tearDown(){

   }
}

My custom Runner is as follows:

public class CustomRobolectricTestRunner extends RobolectricTestRunner {

public CustomRobolectricTestRunner(Class<?> testClass)
        throws InitializationError {
    super(testClass);
    String buildVariant = (BuildConfig.FLAVOR.isEmpty()
            ? "" : BuildConfig.FLAVOR+ "/") + BuildConfig.BUILD_TYPE;
    String intermediatesPath = BuildConfig.class.getResource("")
            .toString().replace("file:", "");
    intermediatesPath = intermediatesPath
            .substring(0, intermediatesPath.indexOf("/classes"));

    System.setProperty("android.package",
            BuildConfig.APPLICATION_ID);
    System.setProperty("android.manifest",
            intermediatesPath + "/manifests/full/"
                    + buildVariant + "/AndroidManifest.xml");
    System.setProperty("android.resources",
            intermediatesPath + "/res/" + buildVariant);
    System.setProperty("android.assets",
            intermediatesPath + "/assets/" + buildVariant);

    ShadowContextWrapper shadowContextWrapper = new ShadowContextWrapper();
    shadowContextWrapper.setApplicationName("appName");
    shadowContextWrapper.setPackageName("package");
    Context context = shadowContextWrapper.getApplicationContext();

    ActiveAndroid.initialize(context);

}

}


Solution

  • So, the issue you're having with your tests is that TestApp is not running. To get it running, you need to setup your test to use a manifest that specifies TestApp as the application to run.

    Setup your TestApp as follows somewhere in /test directory of your source tree... e.g. /src/test/java/some-long-package/TestApp.java:

    package com.some.company;
    
    public class TestApp extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            ActiveAndroid.initialize(this);
        }
    }
    

    This is the important part

    Create an android manifest file in the /test tree of your source. Have this manifest file specify the TestApp as the application. So, create a manifest at a path like /src/test/resources/TestManifest.xml containing the following:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.android.test">
    
            <application android:name="com.some.company.TestApp">
    
        </application>
    </manifest>
    

    I'd recommend getting rid of the CustomRobolectricTestRunner as the default Robolectric 3.0 test runner will do most of what you need done. If you need to test various build variants, use @RunWith(RobolectricGradleTestRunner.class).

    But for now, setup your tests as follows:

    @RunWith(RobolectricTestRunner.class)
    @Config(constants = BuildConfig.class, manifest = "src/test/resources/TestManifest.xml", sdk = Build.VERSION_CODES.LOLLIPOP)
    public class MainAppTest {
    
        @Test
        public void runtimeApplicationShouldBeTestApp() throws Exception {
           String actualName = RuntimeEnvironment.application.getClass().getName();
           String expectedName = TestApp.class.getName();
           assert(actualName).equals(expectedName);
        }
    }
    

    That @Config(manifest= ...) bit will setup Robolectric to use the test manifest and the test application. The test above simple validates that the application context being used within the test is indeed the TestApp.class This will ensure ActiveAndroid is initialized correctly for the test.

    I also agree with Eugen that you may be trying to do a little bit much in your tests. By testing your DB through your application, you are effectively creating an integration test. I'd recommend splitting out the functionality as much as possible.

    Happy testing!