androidgradlerobolectricandroid-assets

How to access files from assets folder during tests execution?


How to access files from assets folder during unit tests execution? My project is build using Gradle, I use Robolectric to run tests. It seems like gradle is being recognizing the assets:

enter image description here

This is how I'm struggling to read the file:

public String readFileFromAssets(String fileName) throws IOException {
    InputStream stream = getClass().getClassLoader().getResourceAsStream("assets/" + fileName);
    Preconditions.checkNotNull(stream, "Stream is null");
    BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
    return IOUtils.toString(reader);
}

But stream is always null. I tried it many different ways, i.e. defined path to a file using different approaches.

Thank you very much in advance.


Solution

  • Basically you have to use Context to read assets. You can not load assets with ClassLoader since it is not in a classpath. I am not sure how you run Robolectric test cases. Here are how I can achieve in both Android studio and gralde command.

    I added separate app-unit-test module to run Robolectric test cases in app project. With proper build configuration and custom RobolectricTestRunner, following test case will pass.

    @Config
    @RunWith(MyRobolectricTestRunner.class)
    public class ReadAssetsTest {
    
        @Test
        public void test_ToReadAssetsFileInAndroidTestContext() throws IOException {
    
            ShadowApplication application = Robolectric.getShadowApplication();
            Assert.assertNotNull(application);
            InputStream input = application.getAssets().open("b.xml");
            Assert.assertNotNull(input);
        }
    
    }
    

    app-unit-test/build.gradle

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:0.14.1'
        }
    }
    
    apply plugin: 'java'
    evaluationDependsOn(':app')
    
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
    
    repositories {
        maven { url "$System.env.ANDROID_HOME/extras/android/m2repository" } // Fix 'com.android.support:*' package not found issue
        mavenLocal()
        mavenCentral()
        jcenter()
    }
    
    dependencies {
        testCompile 'junit:junit:4.8.2'
        testCompile('org.robolectric:robolectric:2.4') {
            exclude module: 'classworlds'
            exclude module: 'commons-logging'
            exclude module: 'httpclient'
            exclude module: 'maven-artifact'
            exclude module: 'maven-artifact-manager'
            exclude module: 'maven-error-diagnostics'
            exclude module: 'maven-model'
            exclude module: 'maven-project'
            exclude module: 'maven-settings'
            exclude module: 'plexus-container-default'
            exclude module: 'plexus-interpolation'
            exclude module: 'plexus-utils'
            exclude module: 'wagon-file'
            exclude module: 'wagon-http-lightweight'
            exclude module: 'wagon-provider-api'
            exclude group: 'com.android.support', module: 'support-v4'
        }
        testCompile('com.squareup:fest-android:1.0.+') {
            exclude group: 'com.android.support', module: 'support-v4'
        }
        testCompile 'org.mockito:mockito-core:1.10.10'
        def appModule = project(':app')
        testCompile(appModule) {
            exclude group: 'com.google.android'
            exclude module: 'dexmaker-mockito'
        }
        testCompile appModule.android.applicationVariants.toList().first().javaCompile.classpath
        testCompile appModule.android.applicationVariants.toList().first().javaCompile.outputs.files
        testCompile 'com.google.android:android:4.1.1.4'
        /* FIXME : prevent Stub! error
            testCompile files(appModule.plugins.findPlugin("com.android.application").getBootClasspath())
            */
        compile project(':app')
    }
    

    Add custom RobolectricTestRunner to adjust file paths. Look at the assets path.

    public class MyRobolectricTestRunner extends RobolectricTestRunner {
    
        private static final String APP_MODULE_NAME = "app";
    
        /**
         * Creates a runner to run {@code testClass}. Looks in your working directory for your AndroidManifest.xml file
         * and res directory by default. Use the {@link org.robolectric.annotation.Config} annotation to configure.
         *
         * @param testClass the test class to be run
         * @throws org.junit.runners.model.InitializationError if junit says so
         */
        public MyRobolectricTestRunner(Class<?> testClass) throws InitializationError {
            super(testClass);
            System.out.println("testclass="+testClass);
        }
    
        @Override
        protected AndroidManifest getAppManifest(Config config) {
    
            String userDir = System.getProperty("user.dir", "./");
            File current = new File(userDir);
            String prefix;
            if (new File(current, APP_MODULE_NAME).exists()) {
                System.out.println("Probably running on AndroidStudio");
                prefix = "./" + APP_MODULE_NAME;
            }
            else if (new File(current.getParentFile(), APP_MODULE_NAME).exists()) {
                System.out.println("Probably running on Console");
                prefix = "../" + APP_MODULE_NAME;
            }
            else {
                throw new IllegalStateException("Could not find app module, app module should be \"app\" directory in the project.");
            }
            System.setProperty("android.manifest", prefix + "/src/main/AndroidManifest.xml");
            System.setProperty("android.resources", prefix + "/src/main/res");
            System.setProperty("android.assets", prefix + "/src/androidTest/assets");
    
            return super.getAppManifest(config);
        }
    
    }
    

    I followed this blog to do it.

    Full example codes are here.