Search code examples
androidtestingfunctional-testingandroid-espresso

How to put test data files on a device for instrumental tests?


What is the best/proper way to upload the test data files onto the device before the instrumentation (Espresso) tests?

I want to test app functionality when a user selects some files on the device file system and my app are processing these files and prepare the app state for further functional testing.

I tried to write it to the file system by the first test right from the test apk assets. However there are problems with permissions. My tested apk does not have the WRITE_EXTERNAL_STORAGE and anyway I'm afraid that is not good way since the modern Android security model with a interactive permission requests to users.

May be I have to integrate it into the gradle script somehow?

Also please note I have 2 types of testing: debug and staging(to test apk with minifyEnabled)

And I'm interested with a way which will be fine for Firebase Test Labs as well.

Thank you.


Solution

  • I found the following way to do that: Create some test class which is responsible for initialization and must be ran first. To run tests in particular order I'm using the Suite class like this one:

    @RunWith(Suite.class)
    @Suite.SuiteClasses({TestEnvironmentInitializer.class,
            FirstTest.class,
            SecondTest.class
    })
    public class MyTestSuite {
    }
    

    It could be run from cmd with:

     ./gradlew yourappmodule:connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=your.package.androidtest.MyTestSuite 
    

    Or if you want to test the apk close to release (stripped with minifyEnabled etc) and are using testBuildType "staging" you may call it:

    ./gradlew yourappmodule:connectedStagingAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=your.package.androidtest.MyTestSuite 
    

    Although there are bunch of other problems not related to this question. E.g. to run tests you have to keep some classes and methods you can remove in the release version, plus apk sign etc.

    For sure you may call it as usually right from Android Studio

    The key problem are permissions. So you just have to use this lines of code in your TestEnvironmentInitializer

    @Rule
    public GrantPermissionRule runtimePermissionRule = GrantPermissionRule.grant(
            Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE);
    

    And of course add this permission into the app AndroidManifest.xml

    My problem was that original tested app should not have the WRITE_EXTERNAL_STORAGE permission.

    And I did not find a good way to resolve this issue. I have tried to add this permission for my test apk only. And logically since my TestEnvironmentInitializer is in the test apk I expected the WRITE permission should be set in the test apk. That could be easily achieved putting the test apk AndroidManifest.xml fragment with this permission under the src/androidTest But it does not help. Because this permission should be set in the original tested apk.

    So I agree for some compromise. And add the debug and staging folder under src/ folder and put the AndroidManifest.xml fragment with WRITE permission there. So when I'm runing my tests they are ran either as debug or staging and the permission is present. However it will not be present in the real release.

    My test data files I have added into the assets folder under src/androidTest

    And here is the sample of my initializer test:

    @RunWith(AndroidJUnit4.class)
    @LargeTest
    public class TestEnvironmentInitializer {
    
        @Rule
        public GrantPermissionRule runtimePermissionRule = GrantPermissionRule.grant(
                Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE);
    
    
        @Test
        public void initEnvironment() {
            try {
    
                Context testContext = InstrumentationRegistry.getInstrumentation().getContext();
    
    
                File root = Environment.getExternalStorageDirectory();
    
    
                File testDataFolder = new File(root, "testData");
    
                if(testDataFolder.mkdirs()) {
    
                    File destFile = new File(testDataFolder, "test_data.dat");
                    if (!destFile.exists()) {
                        copyAssetToExternalStorage(testContext, "assetssubfolder/test_data.dat", destFile);
                    }
                }
    
            } catch (Throwable e) {
                System.exit(-1);
            }
        }
    
        void copyAssetToExternalStorage(Context context, String fileToCopy, File dest) throws
                IOException {
    
            try (InputStream src = context.getAssets().open(fileToCopy)) {
    
                try (FileOutputStream destStream = new FileOutputStream(
                        dest)) {
    
                    copyStream(src, destStream);
                }
            }
        }
    
        void copyStream(InputStream src, OutputStream dest) throws IOException {
    
            byte[] buffer = new byte[1024];
            int read;
            while ((read = src.read(buffer)) != -1) {
                dest.write(buffer, 0, read);
            }
        }
    }
    

    The older devices simply ignore the GrantPermissionRule so it is enough the permission in the app manifest.

    There was another ideas like changing the gradle scripts etc. But I end up with solution above.