sourceSets.main.java.srcDirs entry causes JUnit/Robolectric tests not to find Android resources

223 views
Skip to first unread message

Colin Madere

unread,
Jul 13, 2015, 6:23:10 PM7/13/15
to adt...@googlegroups.com
I'm using the latest gradle android plugin (v1.2.3) and followed some of the latest posts about how to make normal JUnit testing (with Robolectric) work without hacks (https://www.bignerdranch.com/blog/triumph-android-studio-1-2-sneaks-in-full-testing-support/).  I had success on my project which does NOT modify (uses the single default value for) `sourceSets.main.java.srcDirs`.  However, my second project needs to include some generated code and so I added a block like this:

sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/java-gen']
}
}

Doing this seems to cause the Robolectric/Junit tests to fail with "android.content.res.Resources$NotFoundException" on any resource request.  This worked with my previous hack that was copying all kinds of things around, but it seems odd that adding an additional src dir (that doesn't have to do with resources) would cause an error in finding resources.  Tried looking for a bug for this but didn't have luck (just other unrelated "can't find resources" bugs that all have workarounds involving copying resources, such as: https://code.google.com/p/android/issues/detail?id=64887#c13).

Wanted to see if the list had any input/knowledge on this particular problem (or if a bug report already exists that I haven't found).

Thanks,
Colin

Colin Madere

unread,
Jul 13, 2015, 6:35:55 PM7/13/15
to adt...@googlegroups.com
Forgot to add, this is for command line test run with "./gradlew testDebug", not through AS (need this to work on a build system)

Colin Madere

unread,
Jul 13, 2015, 8:02:54 PM7/13/15
to adt...@googlegroups.com
Please excuse my errant post.  Seems I missed the critical fix where Robolectric isn't playing nice with the gradle android way of running tests and requires a special runner to find resources properly.  For anyone else who runs across this issue, you can solve it by using this custom runner:

import org.junit.runners.model.InitializationError;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.res.FileFsFile;
import org.robolectric.util.Logger;
import org.robolectric.util.ReflectionHelpers;

/**
 * Copied from Robolectric v3.0rc2 RobolectricGradleTestRunner
 */
public class MyRobolectricGradleTestRunner extends RobolectricTestRunner {

    private static final String BUILD_OUTPUT = "build/intermediates";

    public TRRobolectricGradleTestRunner (Class<?> klass) throws InitializationError {
        super(klass);
    }

    @Override
    protected AndroidManifest getAppManifest(Config config) {
        if (config.constants() == Void.class) {
            Logger.error("Field 'constants' not specified in @Config annotation");
            Logger.error("This is required when using RobolectricGradleTestRunner!");
            throw new RuntimeException("No 'constants' field in @Config annotation!");
        }

        final String type = getType(config);
        final String flavor = getFlavor(config);
        final String applicationId = getApplicationId(config);

        final FileFsFile res = FileFsFile.from(BUILD_OUTPUT, "res", flavor, type);
        final FileFsFile assets = FileFsFile.from(BUILD_OUTPUT, "assets", flavor, type);

        final FileFsFile manifest;
        if (FileFsFile.from(BUILD_OUTPUT, "manifests").exists()) {
            manifest = FileFsFile.from(BUILD_OUTPUT, "manifests", "full", flavor, type, "AndroidManifest.xml");
        } else {
            // Fallback to the location for library manifests
            manifest = FileFsFile.from(BUILD_OUTPUT, "bundles", flavor, type, "AndroidManifest.xml");
        }

        Logger.debug("Robolectric assets directory: " + assets.getPath());
        Logger.debug("   Robolectric res directory: " + res.getPath());
        Logger.debug("   Robolectric manifest path: " + manifest.getPath());
        Logger.debug("    Robolectric package name: " + applicationId);
        return new AndroidManifest(manifest, res, assets, applicationId);
    }

    private String getType(Config config) {
        try {
            return ReflectionHelpers.getStaticField(config.constants(), "BUILD_TYPE");
        } catch (Throwable e) {
            return null;
        }
    }

    private String getFlavor(Config config) {
        try {
            return ReflectionHelpers.getStaticField(config.constants(), "FLAVOR");
        } catch (Throwable e) {
            return null;
        }
    }

    private String getApplicationId(Config config) {
        try {
            return config.constants().getPackage().getName();
        } catch (Throwable e) {
            return null;
        }
    }

    // ------------------ inner classes ------------------

}

And then annotating all your test classes with:

@RunWith(MyRobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, emulateSdk = 21)

Michal Bendowski

unread,
Jul 14, 2015, 5:48:38 AM7/14/15
to adt...@googlegroups.com
I believe that's a question for the Robolectric devs. Our current unit testing solution considers resource lookups a platform feature that we don't emulate on the JVM at all. If it turns out there are changes needed on our end to make it work, I will be happy to make them.

Michal
Reply all
Reply to author
Forward
0 new messages