woensdag 27 mei 2015

My Robolectric App - Upgraded to Robolectric 3.0 RC2

I finally managed to find some time to upgrade my Robolectric test application to Robolectric 3.0 RC2.

The updated code can be found on GitHub: https://github.com/kvandermast/my-robolectric-app.

Changes to the build.gradle script


First of all, we updated the Gradle Build tools and the Robolectric Gradle Plugin.
...
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.2.3'
        classpath 'org.robolectric:robolectric-gradle-plugin:1.1.0'
    }
}
...

The name of the plugin has changed from 'robolectric' to 'org.robolectric', so that needed to be updated too.
...
apply plugin: 'com.android.application'
apply plugin: 'org.robolectric' //used to be 'robolectric'
...

Next up was the dependencies block: we updated to the latest versions of Robolectric, jUnit and Butterknife. Note that we changed the "androidTestCompile" to "testCompile", and added the "androidTestCompile" for Robolectric (seems to be required to the code compiled in your Android Studio).

...
    // ================== TESTING LIBRARIES ======================
    testCompile 'junit:junit:4.10'
    testCompile 'org.robolectric:robolectric:3.0-rc2'

    testCompile 'com.jakewharton:butterknife:6.1.0'

    androidTestCompile 'org.robolectric:robolectric:3.0-rc2'
...

The following section used to be the 'robolectric' task, which is now called "android.testOptions.unitTests.all".

...
android.testOptions.unitTests.all {
    // configure the set of classes for JUnit tests
    include '**/*Test.class'
    //exclude '**/espresso/**/*.class'

    // configure max heap size of the test JVM
    maxHeapSize = "2048m"
}
...

With these changes, you'll need to synch the project to get the latest libraries. With the upgrade from Robolectric 2.3 to 3.0, quite some changes were made to the library, resulting failed tests. The changes that were made are described in the following section.

Changes to tests

Using a custom RobolectricGradleTestRunnner class

We'll invoke a specific RobolectricTestRunnner which loads the AndroidManifest from the main project.

package be.acuzio.mrta.test;


import org.junit.runners.model.InitializationError;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.res.Fs;

public class RobolectricGradleTestRunner extends RobolectricTestRunner {

    public RobolectricGradleTestRunner(Class testClass) throws InitializationError {
        super(testClass);


    }

    @Override
    protected AndroidManifest getAppManifest(Config config) {
        String manifestProperty = System.getProperty("android.manifest");
        AndroidManifest manifest;


        if (config.manifest().equals(Config.DEFAULT) && manifestProperty != null) {
            String resProperty = System.getProperty("android.resources");
            String assetsProperty = System.getProperty("android.assets");

            manifest = new AndroidManifest(Fs.fileFromPath(manifestProperty), Fs.fileFromPath(resProperty),
                    Fs.fileFromPath(assetsProperty));

        } else {

            String myAppPath = RobolectricGradleTestRunner.class.getProtectionDomain()
                    .getCodeSource()
                    .getLocation()
                    .getPath();

            String manifestPath = myAppPath + "../../../src/main/AndroidManifest.xml";
            String resPath = myAppPath + "../../../src/main/res";
            String assetPath = myAppPath + "../../../src/main/assets";

            manifest = createAppManifest(Fs.fileFromPath(manifestPath), 
                                         Fs.fileFromPath(resPath),
                                         Fs.fileFromPath(assetPath));
        }

        return manifest;
    }
}


Changing the test configuration

The new version of Robolectric supports API levels higher than 18 (woot!). Unfortunately, something isn't quite working as expected on the new Robolectric version (you'll get ResourceNotFound exceptions, which only occur after upgrading from version 2.4 to 3.0). To fix this, pass the BuildConfig class (from your application module, not your test module) in the constants configuration.

...
@Config(emulateSdk = 21, constants = be.acuzio.mrta.BuildConfig.class)
@RunWith(RobolectricGradleTestRunner.class)
public class WidgetActivityTest {
...


Changing the Shadows

In version 2.4 we frequently used the Robolectric.getShadowApplication() method to fetch the Shadowed Application-class, e.g.
...
@Config(emulateSdk = 18)
@RunWith(RobolectricTestRunner.class)
public class MyBroadcastReceiverTest {
     ...

    /**
     * Let's first test if the BroadcastReceiver, which was defined in the manifest, is correctly
     * load in our tests
     */
    @Test
    public void testBroadcastReceiverRegistered() {
        List<ShadowApplication.Wrapper> registeredReceivers = Robolectric.getShadowApplication().getRegisteredReceivers();

        Assert.assertFalse(registeredReceivers.isEmpty());

        boolean receiverFound = false;
        for (ShadowApplication.Wrapper wrapper : registeredReceivers) {
            if (!receiverFound)
                receiverFound = MyBroadcastReceiver.class.getSimpleName().equals( 
                                    wrapper.broadcastReceiver.getClass().getSimpleName());
        }

        Assert.assertTrue(receiverFound); //will be false if the container did not register the broadcast receiver we want to test
    }
...

But in version 3.0, we need to fetch it from the ShadowApplication class, insteadof the Robolectric class:

...
@Config(emulateSdk = 21, constants = be.acuzio.mrta.BuildConfig.class)
@RunWith(RobolectricGradleTestRunner.class)
public class MyBroadcastReceiverTest {
    static {
        // redirect the Log.x output to stdout. Stdout will be recorded in the test result report
        ShadowLog.stream = System.out;
    }

    private ShadowApplication application;

    @Before
    public void setup() {
        this.application = ShadowApplication.getInstance();
    }

    /**
     * Let's first test if the BroadcastReceiver, which was defined in the manifest, is correctly
     * load in our tests
     */
    @Test
    public void testBroadcastReceiverRegistered() {
        //used to be:... registeredReceivers = Robolectric.getShadowApplication().getRegisteredReceivers();
        List<ShadowApplication.Wrapper> registeredReceivers = this.application.getRegisteredReceivers(); 

        Assert.assertFalse(registeredReceivers.isEmpty());
...

Running the tests

With Android Studio 1.2.1.1 (as of 1.1 I believe), it is - finally - possible to run the tests from within the IDE. Previously we needed to run the test from the console like so:

$ ./gradlew testDebug

Which worked perfectly, unless you had a lot of test and little patience to seem them executed one by one.

You can now use the "Unit test" Test Artificat in the build variants.


Running a test is now quite easy, just press "CTRL+R" when in the Test Class, or select "Run '' " from the Context Menu when you right-click the Unit Test.