2

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);

}

}

RaymondMachira
  • 344
  • 2
  • 5
  • 14
  • since it is unit testing I would remove ActiveRecord from tests at all – Eugen Martynov Apr 10 '15 at 09:10
  • 1
    Could you also add in your test code and the manifest file you are using for your tests? I have a sneaking suspicion that there may be something weird going on with the way Robolectric is creating the application – abest Apr 10 '15 at 15:31
  • @EugenMartynov Hey. I thought about doing that. But I have some business logic on my Models, that I would like to test. Plus, I would like to do some database work, like installing fixture data and testing it. Thus, I really need this to work. – RaymondMachira Apr 11 '15 at 03:26
  • @abest I am not sure what manifest you're referring to. I have included my test runner class, as well as one example test. – RaymondMachira Apr 11 '15 at 03:52
  • I should be more clear saying that I would remove dependency to database to all my tests that are not related to database. I would encourage you to read this article https://medium.com/@artem_zin/m-model-from-mvc-mvp-in-android-flow-and-mortar-bd1e50c45395 – Eugen Martynov Apr 11 '15 at 18:57
  • @EugenMartynov Thank you for that resource. I understand the importance of my unit tests being a single unit of work. Unfortunately, the way AA works, DataModel constructors do rely on underlying code that need to be initialized. So I couldn't initialize an object even without performing CRUD. Thank you so much. – RaymondMachira Apr 14 '15 at 18:14
  • @RaymondMachira, no problem. You can still isolate active record in your model class and change active record initialisation to model initialisation – Eugen Martynov Apr 14 '15 at 20:25

1 Answers1

4

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!

Suhas
  • 7,919
  • 5
  • 34
  • 54
abest
  • 724
  • 6
  • 16
  • Thank you. This got me well on the right path to figuring out my problem. Regarding the tests, the way AA is working, the constructor to DataModels won't work without a db. Thus, initialization would fail even without testing CRUD. Once more, thank you. – RaymondMachira Apr 14 '15 at 18:12