So what is automation testing?

It is when the application or function is executed as if a user was using it and a user was initiating a call to it. For example: We could have an application that displays a list of songs, and tapping on one of them would start playing it. So, an automated test, would start the application automatically, and also display the list and perform a click and start playing a song. (It is all automated 🙂 ).

There are many ways by which we can test in android, and many tools available to perform UI Testing, for example: Espresso, Monkey Runner, Robotium, Roboelectric. However, since I am pretty new to testing myself, I thought of jotting down what all I learnt from my first application for everyone else who has a first time too. Though this blog may not be that enriched it still has some basic ideas about how to go about testing in Android for the first time.

I used Android Studio to create my test cases, since it now supports testing in itself unlike eclipse.

The current version of JUnit is version 4, and we can still use version 3 or mix both versions 3 and 4 together but it is not a good idea to mix both of them since that is not the right way to code. Also it becomes difficult to figure out what portion of code caused an issue if at all there is anything like that.

There have been many changes from the older versions of JUnit against the new version. Use of annotations has been increased, making it rather easy to code, and also the lifecycle of the test case has also changed to an extent.

At the end of this article I will mention the sites I used for reference.

JUnit also makes it possible for you to club all the test cases into one suite, such that the entire suite gets executed. So, well the term used here is Suite. It clubs all the instrumentation test cases into one.

There are some tests that are just unit test cases, these are those, than can be executed without the help of android, or any of Android’s framework. These can be executed via command line and the output displayed on the console.

There are other tests that require Android, and these are called Integration test cases, that can be executed on a device, or even an emulator.

Let’s begin with the “build.gradle” file first, for we need to define some dependencies to be able to perform testing.

To make testing possible on devices and emulators we need to specify the instrumentation runner, so we write:

testInstrumentationRunner “android.support.test.runner.AndroidJUnitRunner”
testApplicationId ‘com.dhara.example.test’

to the block “defaultConfig”

The application id of the test APK is always denoted with a test at the end.

Under the dependencies block we therefore have the following dependencies.

  1. If you are just unit testing then you need the following:testCompile ‘junit:junit:4.12’Else, if you would wish to use Android and its functions, then you need the following dependencyandroidTestCompile ‘junit:junit:4.12’In my application I wanted to also perform click events, or rather perform UI Testing, so I used Espresso (It looks promising and it has also been open-sourced by Google)
  1. Since test dependencies require support annotations library, we add the following line. However, since most of the dependencies will have this same library in them we would have to exclude the same from the other libraries to prevent the Dex multiple files error from coming up.androidTestCompile ‘com.android.support:support-annotations:23.1.1’
  1. In order to enable the use of android instrumentation runner, we need test dependencies that support that, so we also add in the following lines.androidTestCompile(‘com.android.support.test:runner:0.4.1’) {
            exclude group: ‘com.android.support’, module: ‘support-annotations’
    }androidTestCompile(‘com.android.support.test:rules:0.4.1’) {
           exclude group: ‘com.android.support’, module: ‘support-annotations’
    }
  1. I had webservice calls to make, but since in testing we generally do not call webservices, we use mock data; I mocked the webservice call using MockWebServer by Square.androidTestCompile(‘com.squareup.okhttp:mockwebserver:2.7.0’) {
            exclude module: ‘okhttp’
    }MockWebServer would mock calls made by retrofit.
  1. Eventually we now add the lines needed for espresso support in our application. Since this application of mine used a RecyclerView and Espresso does not support recycler views in the first dependency, we add in an extra dependency especially for the RecyclerView since it extends aViewGroup unlike a ListView or a GridView.androidTestCompile(‘com.android.support.test.espresso:espresso-core:2.2.1’) {
            exclude group: ‘com.android.support’, module: ‘support-annotations’
    }// support for the recycler view in espresso
    androidTestCompile(‘com.android.support.test.espresso:espresso-contrib:2.2.1’) {
           // Necessary to avoid version conflicts
           exclude group: ‘com.android.support’, module: ‘appcompat’
           exclude group: ‘com.android.support’, module: ‘support-v4’
           exclude group: ‘com.android.support’, module: ‘support-annotations’
           exclude module: ‘recyclerview-v7’
    }
  1. In order to perform better search with the UI elements we can also use Hamcrest, but that is if you really require to do so.// Hamcrest library for easy annotations
    androidTestCompile ‘org.hamcrest:hamcrest-library:1.3’Well, then go ahead and sync your project for the Gradle dependencies added.
    We now move to the code.

Please ensure that your test files are in the folder androidTest, for that is where the compiler would know the test cases reside.

If you would have created a project using Android Studio, you would have the package already with ApplicationTest class already created for you.

The ApplicationTest class is used to test the launch of your application class. This class extends “ApplicationTestCase” and it looks like below:

public class ApplicationTest extends ApplicationTestCase {
    private MyApp myApp;

    public ApplicationTest() {
        super(MyApp.class);
    }

    protected void setUp() throws Exception {
        super.setUp();
        createApplication();
        myApp = getApplication();
    }
}

We specify what class would be tested as the application class, in this case, MyApp being the Application class of the main folder.

setUp() is method available in the super class that we override in order to initialize all the resources that we may need before the application starts.

createApplication() method, starts the application class and getApplication() returns an instance of the application class which can be asserted to not null, just in case you want to test it.

The constructor is necessary for the compiler to detect this as a test case.

Time for me to explain to you my requirement:

I had to test that a list of repositories from the Github API was loaded into the application, where in the user clicks on the first item and the detail gets displayed. I also had to check if the item clicked matched the first element of the JSON Array. So to mock the webservice call I had a file with the list of repositories and for the load more I simply created an arbitrary URL for the webservice call.

I put the file in the assets folder of androidTest package. If you have two assets folder, one in the main package and the other in the test package, you need to ensure you pass the correct context else you will be thrown with an error that the file does not exist.

We now proceed with the testing of the activity, I called it the MainActivity in my application.

The MainActivityTest class will look as under. Since we are using JUnit4 in this blog, we use the annotation @RunWith(AndroidJUnit4.class)

public class MainActivityTest {
    /**
     * A JUnit rule to launch the main activity under test
     * ActivityTestRule will create and launch of the activity for you
     * and also expose the activity under test.
     */
@Rule
    public ActivityTestRule<MainActivity> mActivityRule =
                new ActivityTestRule<>(MainActivity.class, false, false);
}

 

Earlier we had ActivityInstrumentationTestCase2 instead of ActivityTestRule. With ActivityInstrumentationTestCase2 we also have the tearDown() and also setUp() methods that can be overridden, but things have changed with ActivityTestRule.

The lifecycle with ActivityInstrumentationTestCase2 was:

lifecyle_instrumentation_test_case_2

In the case of ActivityTestRule, things changed a little bit. It has 3 constructors:

  1. new ActivityTestRule<>(MainActivity.class);
  2. new ActivityTestTule<>(MainActivity.class, false);
  3. new ActivityTestRule<>(MainActivity.class, false, false) OR a variation of new ActivityTestRule<>(MainActivity.class, fale, true);

The 1st constructor is the default constructor; this means; the Activity that is being tested will be restarted for every test case that has to be executed.

The 2nd constructor specified is the activity should start with or without the touch mode enabled, activity restart would be the set to true.

While, the 3rd constructor – controls whether or not the activity should be restarted for every test to be executed. It can be used to specify different intents that can be used for the test case. I have not experimented much with the second parameter of the 2nd constructor but the activity restart can be controlled by the 3rd parameter.

The lifecycle in the case of ActivityTestRule is almost the same as that of ActivityInstrumentationTestCase2 provided the default constructor is used. We have some methods at our disposal that we can override to perform the same things we could in the setUp() and tearDown() methods.

  • beforeActivityLaunched() – this is where we can now prepare mock calls and also perform method injection if required.
  • getActivityIntent(), this is where we can pass parameters or arguments to the intent.
  • beforeActivityFinished()
  • activityFinished() this is where resources can be released.

lifecycle_actiivty_test_rule

As per the default constructor, an activity will always be started again before a test case is executed, that is, before it begins test execution. And because of this reasons, it may not always be appropriate to initialize mocks before an activity is launched, thus we play around with the 3rd parameter of the other constructor.

However, since we tell the test runner that the activity does not have to be launched for every test that gets executed, we have to explicitly start the activity, or rather launch the activity for our test case to execute.

Using espresso and also Junit4 annotations, we now create test cases, with the use of @Test annotation, thus we do not have to start the method names with the word “test”. Also, we can use the keywords @Before and also @After to continue initializing mock calls in the setUp() and tearDown() methods.

For my example, I had to use the 3rd constructor and @Before annotation to initialize the mock calls. Because if I would have used the default constructor, it would have started the activity before calling @Before method, and the webservice call would have been executed before mock server would have even been injected.

The Code:
Declare:

public void setUp() throws Exception{
    // mock the actual service call and overwrite the base url
    mockWebServer = new MockWebServer();
    mockWebServer.start();
    RestClient.BASE_URL = mockWebServer.url("/").toString();
 }

I use retrofit to make webservice calls, and there is this one class RestClient that creates the retrofit instance to make the webservice calls, setting the converter for the response, and also the base URL to make the service calls. Since we are not calling a webservice in the test class and only fetching data from the file in the assets folder, we overwrite the URL to point at “/” or rather a Localhost URL provided by MockWebServer.

MockWebServer, is an appropriate library to mock the webservice calls especially if using retrofit.

I combined all my tests into just one test class.

We create a method called initiateMockServerCalls () and it would look like this:

private void initiateMockServerCalls() throws IOException {
// given that the next link is as under we pass the header
// since=100000000000000 to get an empty list for the next pagination call
String link = "<https://api.github.com/repositories?since=100000000000000>;
rel=\"next\"," +
"<https://api.github.com/repositories?since=0>; rel=\"previous\"";

String fileName = "repositories.json";
MockResponse mockResponse = new MockResponse();

mockResponse.setHeader("Link", link);

// set the body as the text from the json file stored in the assets folder
// we use #InstrumentationRegistry.getContext() to be able to access the assets folder of the test application
mockWebServer.enqueue(mockResponse
.setResponseCode(200)
.setBody(RestClientTestHelper.getStringFromFile(
InstrumentationRegistry.getContext(), fileName)));

// since the ActivityTestRule's third constructor is used,
// for the test to be executed we launch the activity
Intent intent = new Intent();
mActivityRule.launchActivity(intent);
}

There are a couple of things to explain in this method:

Once setup will get called before the activity is launched, we would have the base URL overwritten with the one created by mock web server, and then we have the test method executed, method with the annotation @Test.

The start of this method would have the method initiateMockServerCalls .

Since we have the file in the assets folder, and since you might have the assets folder in your main application too, it is important to note the context that you pass in order to read the file, thus we use InstrumentationRegistry.getContext() instead of InstrumentationRegistry.getTargetContext() since the later one would point to the context of the main application, and thus return with an IOException that the file was not found.

Since, the webservice call I was making also had pagination implemented, and the method in the activity had a function that read the header too,  so, I passed in dummy headers here, that looked exactly like what I would have obtained in the wbeservice call.

MockWebServer.enqueue method would add this mock call to its queue for execution, and setBody is used to mock the response that we would have obtained from the json file.

RestClientTestHelper.getStringFromFile() this method is a static method in the class RestClientTestHelper that reads the data from the assets file and converts the InputStream to a string which is then returned.

Once the server call is mocked, we then launch the activity, so that the mock server call can be made, instead of the actual web service.

Let’s move on:

@Test
public void testLoadMoreAndItemClick() throws IOException, InterruptedException {
initiateMockServerCalls();

// wait until the records are shown, added this for the test in the emulator
Thread.sleep(3000);

// given the first item is clicked on
onView(withId(R.id.recylerView)).perform(
RecyclerViewActions.actionOnItemAtPosition(0, ViewActions.click()));

// the detail screen is displayed here, so we test if the full repo name is shown correctly or not
onView(withId(R.id.txtRepoFullName))
.check(matches(withText(("collectiveidea/clear_empty_attributes"))));

// press back and then perform load more
onView(withContentDescription(
R.string.abc_action_bar_up_description)).perform(ViewActions.click());

// we scroll to the end of the list
// given the number of items in the adapter is 9
onView(withId(R.id.recylerView))
.perform(RecyclerViewActions.scrollToPosition(9));

// espresso does not require thread.sleep method, but i could get this to wait for an async task to be completed
// to prevent dynamic_resource_idle error
Thread.sleep(5000);
}

Like mentioned above, I used espresso to test the UI events.

onView(withId(R.id.recylerView))
.perform(RecyclerViewActions.actionOnItemAtPosition(0,
ViewActions.click()));

onView would fetch the view with the Id that you pass in the withId method, and the perform method of the view can be used to perform the actions you wish to do. The perform method uses ViewActions as the parameter provided by espresso.

Since we want to click the first item we use RecyclerView’s actionOnItemAtPosition method that takes up the click.

onView(withId(R.id.txtRepoFullName))
.check(matches(withText(("collectiveidea/
clear_empty_attributes"))));

Once the item is clicked at position 0, the detail view would open automatically based on your code, and the above line would then check if the TextView for example, has the information the same as the first element’s data. You can modify this according to your code.

onView(withContentDescription(R.string.abc_action_bar_up_description))
.perform(ViewActions.click());

In order to test the load more functionality I had to move back to the previous screen to enable a scroll event, so I used the code above. It is not possible to hit the back button of the toolbar (the action bar up button), with the use of the id, hence we use the description of the navigation up button.

The code, in layman’s language simply means, for the view having content description similar to the string value, we wish to perform a click event. And in your code, you could call onBackPressed in the back button click.

On back press we then call in a method to scroll to the bottom of the list using

onView(withId(R.id.recylerView))
.perform(RecyclerViewActions.scrollToPosition(9));

We pass in 9 since the JSON file returns 10 elements only.

At the end, once the test gets executed, we then have the tearDown method that gets called, where in we shut down the mock server we just used.

@After
public void tearDown() extends Exception {
mockWebServer.shutdown();
}

There is a lot that can be done with JUnit4 and Espresso together, and it is always fun experimenting with new things, so then move ahead and play with android automated testing tools.

Please note:

  • In the case of automated testing, we do not have anything that can test a fragment like FragmentTestCase, so in order to test fragments, you can have a blank activity with a container, and in the onCreate() you could simply be replacing that container with your fragment. Create a test that tests the activity using ActivityTestRule, and all the UI actions can be testing like normal as if you are testing it on the fragment, since you would be accessing the id of the UI element in the fragment only.
  • We cannot combine test cases since test cases run on their own. So that means we cannot access an object of one test class in the other, and also we cannot use the application context created in the ApplicationTest class in any other test class that we create
  • When working with AdapterViews and Espresso, instead of using “onView(withId(R.id.id_here))” you should use Espresso.onData() method, since if the elements are not loaded into the listview or the gridview, Espresso might return null for the same.

You can comment in case there is any error or there is anything i might have missed out 🙂

References:

  1. Using junit access application class in test case
  2. How do i test the home button on the action bar with espresso
  3. Espresso test life cycle
Advertisements