125

In a unit test, how can I read data from a json file on my (desktop) file system, without hardcoding the path?

I would like to read test input (for my parsing methods) from a file instead of creating static Strings.

The file is in the same location as my unit testing code, but I can also place it somewhere else in the project if needed. I am using Android Studio.

Frank
  • 12,010
  • 8
  • 61
  • 78
  • 3
    I tried almost every combination with IOUtils.toString( this.getClass().getResourceAsStream("test_documents.json"), "UTF-8"), it always returns null. Probably because the files won't get included in the jar. – Frank Apr 01 '15 at 09:39
  • Are we talking about unit tests involving android emulator/device? – AndroidEx Apr 05 '15 at 22:32
  • @Android777 I think we're talking about new unit test support introduced in recent version of Android Studio http://tools.android.com/tech-docs/unit-testing-support – akhy Apr 06 '15 at 05:59
  • @Frank where do you place `test_documents.json`? assets directory? – akhy Apr 06 '15 at 06:02
  • Yes, we are talking about the new unit test support, not involving the emulator/device. I did not place it in the assets dir, because then it gets packaged with the live apk. I placed it in the same folder as the test (java) files. – Frank Apr 06 '15 at 10:06

9 Answers9

170

Depending on android-gradle-plugin version:

1. version 1.5 and higher:

Just put json file to src/test/resources/test.json and reference it as

classLoader.getResource("test.json"). 

No gradle modification is needed.

2. version below 1.5: (or if for some reason above solution doesn't work)

  1. Ensure you're using at least Android Gradle Plugin version 1.1. Follow the link to set up Android Studio correctly.

  2. Create test directory. Put unit test classes in java directory and put your resources file in res directory. Android Studio should mark them like follow:

    enter image description here

  3. Create gradle task to copy resources into classes directory to make them visible for classloader:

    android{
       ...
    }
    
    task copyResDirectoryToClasses(type: Copy){
        from "${projectDir}/src/test/res"
        into "${buildDir}/intermediates/classes/test/debug/res"
    }
    
    assembleDebug.dependsOn(copyResDirectoryToClasses)
    
  4. Now you can use this method to get File reference for the file resource:

    private static File getFileFromPath(Object obj, String fileName) {
        ClassLoader classLoader = obj.getClass().getClassLoader();
        URL resource = classLoader.getResource(fileName);
        return new File(resource.getPath());
    }
    
    @Test
    public void fileObjectShouldNotBeNull() throws Exception {
        File file = getFileFromPath(this, "res/test.json");
        assertThat(file, notNullValue());
    }
    
  5. Run unit test by Ctrl+Shift+F10 on whole class or specyfic test method.
klimat
  • 24,711
  • 7
  • 63
  • 70
  • 4
    It's a nice solution, but it doesn't work always. If you perform run the clean task, and then run testDebug it fails. Basically the developer needs to know that he must run the assembleDebug task before testDebug. Do you guys have any suggestion to improve this? – joaoprudencio Jul 29 '15 at 10:24
  • This solution worked for me WITHOUT the gradle changes by putting the files under test/res/assets and getClass().getClassLoader().getResourceAsStream("assets/" + jsonFile) – dweebo Oct 27 '15 at 13:28
  • 24
    With AndroidStudio 1.5 with android plugin 1.5.0 place your json file here `src/test/resources/test.json` and reference it as `classLoader.getResource("test.json")`. No gradle modification is needed. Nested folders inside `resources` are working also. – Sergii Pechenizkyi Nov 30 '15 at 22:04
  • @joaoprudencio can you make this task depend on a task that is run every time you run a unit test? – Bhargav Dec 03 '15 at 10:03
  • This isn't working for me please see my question here http://stackoverflow.com/questions/36295824/how-can-i-load-a-json-file-from-an-android-unit-test-that-is-run-with-powermockr – Tyler Pfaff Mar 29 '16 at 22:30
  • This definitely works and is good - but now I see a "resources" folder in my app hierarchy. It's not clear that it's associated with test resources at all (i.e. no (test) label next to it like the test java folders are). Is there a way to "clean" this up? Maybe move the location of this folder? add that label so I don't get confused with the "res" folder that Android uses for regular app resources, etc? – Zach Dec 16 '16 at 19:14
  • `Could not get unknown property 'assembleDebug' for project ':app' of type org.gradle.api.Project.` any idea? – M. Reza Nasirloo Mar 05 '17 at 15:14
  • 11
    Incomplete answer. What is `classLoader`? where to place this line of code? what can you do with the return result?. Please provide more complete code. -1 – voghDev Mar 15 '17 at 09:56
  • That's true, but you could at least complete the code with the test method header (so reader knows it goes inside a test), and assign it to an InputStream, or any other object as other answers did. Honestly, I read the answer this morning and had to spend time reading the method signature, documentation, etc. I would expect something more helpful. That's why I encourage you to improve it – voghDev Mar 15 '17 at 18:09
  • @ joaoprudencio Commenting out testCoverageEnabled true did the trick for me. buildTypes { debug { // testCoverageEnabled true } } – DoronK May 16 '17 at 11:41
  • when nesting resources, use a forward slash (/) even on Windows – Les Aug 22 '17 at 17:06
  • If you get NullPointerException or NoSuchFileException - make sure your directory is called resources, not res. And here's the way you get this file URL resource = this.getClass().getClassLoader().getResource("file.txt"); File f = new File(resource.toURI()); – Ihor Klimov Dec 17 '17 at 12:53
  • Just in case anyone else struggles, I was confused for a little while because I was adding the folder while in "Android" view of the project, instead of "Project" view. As a result, I'd created it as src/test/java/com.example/resources - it was with all my tests, instead of in the root of the tests. – Rob Drimmie May 01 '18 at 13:05
  • 8
    To read `./src/test/resources/file.txt` in Kotlin: `TestClassName::class.java.getResource("/file.txt")!!.readText()` – Erik Nov 21 '18 at 17:10
  • Caused by: java.lang.IllegalArgumentException: UTF8 string too large, not the best way to do it – desgraci Dec 15 '19 at 10:22
  • @joaoprudencio only replace "assembleDebug" with "build". – Guillermo Tobar Mar 30 '21 at 13:36
  • please can I convert this to a bitmap? – Ben Ajax Oct 17 '22 at 16:45
86

For local unit tests (vs. instrumentation tests), you can put files under src/test/resources and read them using classLoader. For example, following code opens myFile.txt file in the resources directory.

InputStream in = this.getClass().getClassLoader().getResourceAsStream("myFile.txt");

It worked with

  • Android Studio 1.5.1
  • gradle plugin 1.3.1
user3113045
  • 3,243
  • 2
  • 16
  • 10
  • This isn't working for me please see my question here http://stackoverflow.com/questions/36295824/how-can-i-load-a-json-file-from-an-android-unit-test-that-is-run-with-powermockr – Tyler Pfaff Mar 29 '16 at 22:30
  • 5
    This worked for me, when I changed from "src/test/res" to "src/test/resources". Using Android Studio 2.0 RC 3, gradle:2.0.0-rc3 – Alex Bravo Apr 06 '16 at 15:45
  • 1
    worked as expected. But test is getting pass even if file is not avail under "src/test/resources/" eg:"rules.xml", but InputStream results null in this case. – anand krish Oct 23 '17 at 10:19
  • 12
    kotlin: `val myFile = ClassLoader.getSystemResource("myFile.txt").readText()` – jj. Feb 10 '19 at 20:08
  • for my case as I need to read it as input stream in kotlin, I use this: `ClassLoader.getSystemResourceAsStream("myFile.txt")` – Bruce Aug 23 '23 at 05:47
18

In my case, the solution was to add to the gradle file

sourceSets {
    test.resources.srcDirs += 'src/unitTests/resources'
  } 

After it everything was found by AS 2.3.1

javaClass.classLoader.getResourceAsStream("countries.txt")
ar-g
  • 3,417
  • 2
  • 28
  • 39
  • 4
    I did something very similar (Kotlin): 1. create folder and file at: `root/app/src/test/resources/test.json` 2. read data like this: `val jsonFile = ClassLoader.getSystemResource("test.json").readText()` – jj. Feb 10 '19 at 20:10
  • After a very long search and many attempted solutions, this was the on that did the trick for me. Thank you so much @ar-g! – Clark Sandholtz Mar 22 '23 at 01:39
17

I though I should add my findings here. I know this is a little old but for the newer versions of Gradle, where there is NO src/test/resources directory, but only one single resources directory for the whole project, you have to add this line to your Gradle file.

android {
   testOptions {
      unitTests {
         includeAndroidResources = true
      }
    }
}

By doing this you can access your resource with:

 this.getClass().getClassLoader().getResourceAsStream(fileName);

I've been searching for this and could not find an answer, so I decided to help others here.

Raphael Ayres
  • 864
  • 6
  • 16
  • thank you Raphael Ayres. This should be the top voted comment. Also, you need to create your resources directory under src/test/, so you'll end up having src/test/resources/... – Martin Nowosad Oct 22 '20 at 13:21
10

I've had plenty of problems with test resources in Android Studio so I set up a few tests for clarity. In my mobile (Android Application) project I added the following files:

mobile/src/test/java/test/ResourceTest.java
mobile/src/test/resources/test.txt
mobile/src/test/resources/test/samePackage.txt

The test class (all tests passes):

assertTrue(getClass().getResource("test.txt") == null);
assertTrue(getClass().getResource("/test.txt").getPath().endsWith("test.txt"));
assertTrue(getClass().getResource("samePackage.txt").getPath().endsWith("test/samePackage.txt"));
assertTrue(getClass().getResource("/test/samePackage.txt").getPath().endsWith("test/samePackage.txt"));
assertTrue(getClass().getClassLoader().getResource("test.txt").getPath().endsWith("test.txt"));
assertTrue(getClass().getClassLoader().getResource("test/samePackage.txt").getPath().endsWith("test/samePackage.txt"));

In the same root project I have a Java (not Android) project called data. If I add the same files to the data project:

data/src/test/java/test/ResourceTest.java
data/src/test/resources/test.txt
data/src/test/resources/test/samePackage.txt

Then all the tests above will fail if I execute them from Android Studio, but they pass on the command line with ./gradlew data:test. To get around it I use this hack (in Groovy)

def resource(String path) {
    getClass().getResource(path) ?:
            // Hack to load test resources when executing tests from Android Studio
            new File(getClass().getClassLoader().getResource('.').path
                    .replace('/build/classes/test/', "/build/resources/test$path"))
}

Usage: resource('/test.txt')

Android Studio 2.3, Gradle 3.3

Love
  • 1,709
  • 2
  • 22
  • 30
  • Great answer (after 45 mins of searching ). It gets to the heart of several issues and makes it easy to replicate the results using the tests themselves. Fwiw, In AS 2.3.1, Gradle 2.3.1, the `getClassLoader()` versions work as expected. I.e. they find the files, both run from Android Studio AND from the command line on my macOS machine, and on the Linux CI server (CircleCI). So I'm going with that and hoping Gradle 3.3 doesn't break it later... – mm2001 May 06 '17 at 19:12
  • Nice! I'm seeing the same resource loading issue here. AS 2.3.2, Gradle 3.3. The tests work from the command line but not through AS, due to `build/resources/test` not being included in the interactive classpath. – Mark McKenna May 17 '17 at 14:18
6

If you go to Run -> Edit configurations -> JUnit and then select the run configuration for your unit tests, there is a 'Working directory' setting. That should point to wherever your json file is. Keep in mind this might break other tests.

Tas Morf
  • 3,065
  • 1
  • 12
  • 8
  • and than use this.getClass().getResourceAsStream(test.json) ? I added the files there, in the root of the project, it still does not find the files. – Frank Apr 07 '15 at 15:01
  • This can be used to load files using `new File()` or similar, but it doesn't work directly with the classpath loading method described above. It's also a bit tricky because each new run configuration needs to set the working dir, and by default AS and Gradle command line like to use different working dirs... such a pain – Mark McKenna May 17 '17 at 14:28
6

Actually, this is what worked for me with Instrumentation Tests (Running Android Studio version 3.5.3, Android Gradle Plugin version 3.5.3, Gradle version 6.0.1):

  1. Put your files in src/androidTest/assets folder
  2. In your test file:

InputStream is = InstrumentationRegistry.getInstrumentation().getContext().getAssets().open("filename.txt");

Michele
  • 751
  • 1
  • 8
  • 16
  • 2
    This works fine for instrumented tests. Did this work for you in the unit test (no instrumentation test)? – display name May 04 '21 at 21:05
  • @displayname Watch [this question](https://stackoverflow.com/questions/70741657/local-test-open-a-file-in-the-assets-directory). I wondered the same thing. – Damn Vegetables Jan 17 '22 at 13:08
5

In my case I only needed to create resources folder into test folder and put my resource file in that folder. Then, in the test simply load the file as resource stream with:

val inputStream =
        this.javaClass.classLoader?.getResourceAsStream("gallery.xml")

enter image description here

Reference on medium: https://medium.com/mobile-app-development-publication/android-reading-a-text-file-during-test-2815671e8b3b

Nicola Gallazzi
  • 7,897
  • 6
  • 45
  • 64
3

Step(1) open Android Studio select Project view

Step(2) and create resources directory under test.

Please look at attached screenshot for Step(2) result

enter image description here

Step(3) put json file into resources folder(app/src/test/resources)

Please look at attached screenshot for Step(3) result

enter image description here

Step(4) Create a common class to handle reading local json files and converting to respective models using Gson

Example :-

object TestHelper {
    private val gson = Gson()

    fun loadJsonAsString(fileName: String): String {
        val inputStream = javaClass.getResourceAsStream("/$fileName")
        return getStringFromInputStream(inputStream)
    }

    @Throws(IOException::class)
    private fun getStringFromInputStream(stream: InputStream?): String {
        var n = 0
        val buffer = CharArray(1024 * 4)
        val reader = InputStreamReader(stream, "UTF8")
        val writer = StringWriter()
        while (-1 != reader.read(buffer).also { n = it }) writer.write(buffer, 0, n)
        return writer.toString()
    }

    fun <T> convertJsonToModel(jsonString: String, classT: Class<T>): T{
        return gson.fromJson(jsonString, classT)
    }
}

Step(5) load the json file stored locally in resources directory created in Step(2)

val GET_USER_INFORMATION_RESPONSE_FILE_NAME = "user_response.json"
val jsonString = loadJsonAsString(GET_USER_INFORMATION_RESPONSE_FILE_NAME)
        val networkStatusResponse =
            convertJsonToModel(jsonString, UserResponse::class.java)

Step(6) at the end of Step(5) you would have converted local json file into required model class that can be used to write your unit tests.