RSS Feed
Jun 30

How to create Shadow classes in Robolectric 3

Posted on Tuesday, June 30, 2015 in Coding

Mobile application testing is one very important feature of app development. So is diagnostic logging for debugging purposes. Robolectric is one of the good frameworks out there for running very fast tests of your Android application inside the Java virtual machine.

Robolectric can be extended by use of shadow classes which modify the behaviour of certain classes in the Android OS. However, your application also depends on other libraries which are not included in the Android framework. Fortunately, Robolectric gives you the means to create shadow classes for external libraries which you use so you can modify or extend the behaviour of the library during testing.

Shadow classes for classes in the Android framework are automatically picked up and loaded by Robolectric during testing, but shadow classes for third-party libraries need a bit more work for Robolectric to use them. In Robolectric 3, the way to create shadow classes has changed from previous versions. There isn’t much documentation for the new release yet, so after some experimentation, I found a way to create shadow classes which I am documenting here for future reference.

In my case, I was creating a shadow class for the Crashlytics logging library so that I could disable all logging to the service during testing. I created my shadow class as follows:

@Implements(Crashlytics.class)
public class ShadowCrashlytics {

    @Implementation
    public static void start(Context context){
        System.out.println("Shadowing crashlytics start");
        //nothing to see here, move along
    }
}

The @Implements annotation allows you to specify which class you are shadowing. Also you need to implement the methods with the exact same signatures as in the original class. In this case, I only needed one method, the start() method which should do nothing.

Next thing to do is to implement a test runner which you can use to load the tests into the app. An example of such class looks like this (code adapted from this link):

public class GnucashTestRunner extends RobolectricGradleTestRunner {

    private static final List<String> CUSTOM_SHADOW_TARGETS =
            Collections.unmodifiableList(Arrays.asList(
                    "com.crashlytics.android.Crashlytics"
            ));
    public GnucashTestRunner(Class<?> klass) throws InitializationError {
        super(klass);
    }

    @Override
    protected ShadowMap createShadowMap() {
        return super.createShadowMap()
                .newBuilder().addShadowClass(ShadowCrashlytics.class).build();
    }

    @Override
    public InstrumentingClassLoaderConfig createSetup() {
        return new InstrumenterConfig();
    }

    private class InstrumenterConfig extends InstrumentingClassLoaderConfig {

        @Override
        public boolean shouldInstrument(ClassInfo classInfo) {
            return CUSTOM_SHADOW_TARGETS.contains(classInfo.getName())
                    || super.shouldInstrument(classInfo);
        }

    }

}

The test runner needs to be declared in your Gradle file in the defaultConfig section as follows:

testInstrumentationRunner "org.gnucash.android.test.ui.GnucashAndroidTestRunner"

Also, all the test classes should have the annotation:

@RunWith(GnucashTestRunner.class)
@Config(constants = BuildConfig.class, shadows = {ShadowCrashlytics.class})

Note that we declare our shadow class in the annotation so that it is loaded. If you have more than one shadow class, add it to the list separated by commas.

And that’s it! Now when you run your tests, your custom shadow classes should be loaded and executed.