23

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.

HpTerm
  • 8,151
  • 12
  • 51
  • 67
Eugene
  • 59,186
  • 91
  • 226
  • 333
  • Does this apply? http://stackoverflow.com/questions/20184480/loading-assets-in-an-android-test-project – Peter Niederwieser Nov 07 '14 at 16:21
  • No, it isn't InstrumentationTestCase – Eugene Nov 07 '14 at 16:29
  • Well, perhaps it needs to be. – Peter Niederwieser Nov 07 '14 at 16:46
  • I never used Unit Test but probably the problem is the same as my answer here : http://stackoverflow.com/questions/9544737/read-file-from-assets/9544781#9544781 accessing the asset folder is not possible directly, you have to use getAssets() to have it working. However as I never used Unit Test, my comment is perhaps irrelevant. In that case tell me and I will remove it. – HpTerm Nov 12 '14 at 13:55
  • Check this question http://stackoverflow.com/questions/24631428/using-assets-in-robolectric-tests – Qw4z1 Nov 17 '14 at 08:49

9 Answers9

16

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

Andrew Panasiuk
  • 626
  • 1
  • 8
  • 17
15

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.

Pang
  • 9,564
  • 146
  • 81
  • 122
Chk0nDanger
  • 1,211
  • 1
  • 10
  • 26
7

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

schnatterer
  • 7,525
  • 7
  • 61
  • 80
Nicks
  • 16,030
  • 8
  • 58
  • 65
  • 1
    Doesn't work for me, `RuntimeEnvironment.application.getAssets().open` returns an `IOException`, and I add assets folder as: https://gist.github.com/nebiros/4d6370c3ba87f3dd6f6a. – nebiros Dec 11 '15 at 14:31
  • 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). – Bao-Long Nguyen-Trong Apr 19 '16 at 00:36
7

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

InstrumentationRegistry.getContext().getAssets().open(filePath);
Sam YC
  • 10,725
  • 19
  • 102
  • 158
  • This `InstrumentationRegistry` is deprecated so this is not the correct syntax anymore. – Joakim Engstrom Jul 10 '19 at 18:14
  • The new replacement for `androidx.test.InstrumentationRegistry` is `androidx.test.platform.app.InstrumentationRegistry` see here https://developer.android.com/reference/androidx/test/platform/app/InstrumentationRegistry.html – JuanMoreno Feb 03 '20 at 12:54
1

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

JuanMoreno
  • 2,498
  • 1
  • 25
  • 34
1

If you are using Kotlin, you can have something like this:

getInstrumentation().targetContext.assets.open("news.json")

instead of .context, you can simply use targetContext

iman kazemayni
  • 1,255
  • 1
  • 19
  • 20
0

Did you try placing the assets folder under main instead of androidTest?

Also, are you using https://github.com/robolectric/robolectric-gradle-plugin to run your tests?

kageiit
  • 61
  • 1
  • 3
0

Try adding it to your sourceSets in your app build.gradle

android {

    sourceSets {
        test.resources.srcDirs += 'src/androidTest/assets'
    }
}
Bakon Jarser
  • 703
  • 6
  • 20
-1

If everything is correct, then you'll need something like this to read it:

public String readFileFromAssets(String fileName, Context context) throws IOException {
    InputStreamReader stream = new InputStreamReader(context.getAssets().open(fileName));
    Preconditions.checkNotNull(stream, "Stream is null");
    BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
    return IOUtils.toString(reader);
}

You need to pass Context for it to work.

Another thing to verify if you have configured assets in Gradle correctly? Here is just an example:

sourceSets {
    main {
        java.srcDirs = ['src/main']
        // can be configured in different way
        assets.srcDirs = ['src/androidTest/assets']
        // other things, examples
        res.srcDirs = ['res']
        manifest.srcFile 'AndroidManifest.xml'
    }
}
Igor K
  • 470
  • 6
  • 10