How to access files from assets folder during tests execution?

22,853

Solution 1

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.

Solution 2

I've just got stuck with the same issue and here is how it works for me.

I put test files to src/test/resources folder instead of assets folder.

Then I get these files as stream in following way

private InputStream openFile(String filename) throws IOException {
       return getClass().getClassLoader().getResourceAsStream(filename);
}

filename is the relative path to the file inside resources folder.

That's it. I've found solution at Robolectric github

Solution 3

With the latest Android Instrumentation Test, you can just use this:

InstrumentationRegistry.getContext().getAssets().open(filePath);

Solution 4

Updating as for Roboelectric 3.1

    @Test
    public void shouldGetJSONFromAsset() throws Exception{
         Assert.assertNotNull(RuntimeEnvironment.application); //Getting the application context
         InputStream input = RuntimeEnvironment.application.getAssets().open("fileName.xml");// the file name in asset folder
         Assert.assertNotNull(input);
        }

See also

  1. 2.4 to 3.0 Upgrade Guide
  2. 3.0 to 3.1 Upgrade Guide

de Guide

Solution 5

When Robolectric versions previous to 4.0 we need to take in count that:

By default, Robolectric will assume that your resources and assets are located in directories named res and assets, respectively. These paths are assumed to be relative to the directory where the manifest is located. You can change these values by adding the resourceDir and assetDir options to the @Config annotation.

See the official docs

Share:
22,853

Related videos on Youtube

Eugene
Author by

Eugene

Updated on April 06, 2022

Comments

  • Eugene
    Eugene about 2 years

    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.

  • Eugene
    Eugene over 9 years
    this way they would compile in application which I need to avoid.
  • nebiros
    nebiros over 8 years
    Doesn't work for me, RuntimeEnvironment.application.getAssets().open returns an IOException, and I add assets folder as: gist.github.com/nebiros/4d6370c3ba87f3dd6f6a.
  • Bao-Long Nguyen-Trong
    Bao-Long Nguyen-Trong about 8 years
    Works for me. However, this opens files in the assets folder of the main sourceset and not the test one (which is what I am looking for).
  • Joakim Engstrom
    Joakim Engstrom almost 5 years
    This InstrumentationRegistry is deprecated so this is not the correct syntax anymore.
  • WindRider
    WindRider over 4 years
    This is no longer actual in the current version (4.0).
  • JuanMoreno
    JuanMoreno over 4 years
    The new replacement for androidx.test.InstrumentationRegistry is androidx.test.platform.app.InstrumentationRegistry see here developer.android.com/reference/androidx/test/platform/app/…