Friday, 13 March 2015

Working with Robolectric on Android - A basic guide

1. Introduction

Few days ago I created a guide which shows how to install Robolectric, Mockito and AssertJ in 
Android Studio.  Now I am going to show how the basic features of Robolectric.

The source code of the project could be find in the branch RobolectricBasicGuide of the project unitTestingWithAndroidStudio in GitHub.

Note that the version of Robolectric that I use is 3.0, which has some syntactical differentiation with 2.X. You can check the differences here:
https://github.com/robolectric/robolectric/wiki/2.4-to-3.0-Upgrade-Guide-(Draft)

2. Test lifecycle and other notations

As all the tests of JUnit, tests on Robolectric has a specific lifecycle. This is, instead of name the method as setUp, teardown, and testX, robolectric has notations as @Before, @After and @Test, respectively.

The advantage of this is we can name the test with more meaningful name.

The another important notation is @RunWith. This notation is placed before the class is created and it specifies the customised runner which should run for the test instead of the default runner. In my case I have created a new Runner called RobolectricGradleTestRunner, by extending the default runner and overriding several methods, so I put the follow sentence before the declaration of the class.

@RunWith(RobolectricGradleTestRunner.class)

Last but not least is the use of the @Config notation. This notation is used in the tests to set a specific environment to run the test, such as Locale setting, the shadow classes or the position of the device (landscape, portrait).

More information about @Config could be found here:

3. Instantiate the activity

The second thing to do with Robolectric is create the activity. Robolectric has the control over all the lifecycle of the activity, such as onCreate, onPause or onStop. You can have more information here:

To create a simple version of the activity, which only the method onCreated is called, do the follow:

private MainActivity mMainActivity;

@Before
public void setup() {
    mMainActivity = Robolectric.buildActivity(MainActivity.class).create().get();
}

There could be activities which requires a specific intent or intents with a specific value to be instantiated. To do so, create a new intent and before invoke the method create(), invoke the method with(myIntent) to start the activity with the specific intent.

private MainActivity mMainActivity;

@Before
public void setup() {
    Intent startMainActivityIntent = new Intent(
            ShadowApplication.getInstance().getApplicationContext(), MainActivity.class);
    // Here you can add any data to the intent

    mMainActivity = Robolectric.buildActivity(MainActivity.class)
            .withIntent(startMainActivityIntent).create().get();
}

4. Check the elements

Once the activity is created as created properly, it is time to create the tests.

The simple test that could be done is check if the activity and all the views related with the activity exists.

To do so, first we check if the activity has been correctly created with the follow sentence:

assertThat(mMainActivity).isNotNull();

Then, per each one of the elements, we do the follow:

EditText mFirstNumberET = (EditText)mMainActivity.findViewById(R.id.first_number_et);
assertThat(mFirstNumberET).isNotNull();

The code above check two things:
1. If the view created could be casted to EditText
2. If the view with such id exists.

Note that I invoke the method findViewById(int) of the instantiated activity. This is because the test is a simple jUnit test and the method findViewById(int) only exists in the Activity class.

5. View change tests

Once the view test has been done, we can do the tests related with the view changes. This is basically after setting some data and/or interact with the views, check the views has been changed correctly.

First of all, a simple initiation check test is needed.

EditText firstNumberET = (EditText)mMainActivity.findViewById(R.id.first_number_et);
assertThat(firstNumberET.getText()).isEqualTo("0");

The code above get the EditText with the id first_number_et, get the text and assert it should be equal to 0.

Once the test is done, a better test could be created. Like the follow one:

/**
 * This test check if the calculator could do the follow operation:
 *     1 + 2 = 3
 */
@Test
public void simpleSumTest() {
    int firstNumber = 1;
    int secondNumber = 2;

    Calculator calculator = new Calculator(firstNumber, secondNumber);
    int result = calculator.sum();

    EditText firstNumberET = (EditText)mMainActivity.findViewById(R.id.first_number_et);
    firstNumberET.setText(String.valueOf(firstNumber));

    EditText secondNumberET = (EditText)mMainActivity.findViewById(R.id.second_number_et);
    secondNumberET.setText(String.valueOf(secondNumber));

    Button calculateBtn = (Button)mMainActivity.findViewById(R.id.calculate_btn);
    calculateBtn.performClick();

    TextView resultTV = (TextView)mMainActivity.findViewById(R.id.result_tv);
    assertThat(resultTV.getText()).isEqualTo(String.valueOf(result));
}

The test above assigns 1 to firstNumberET, assigns 2 to secondNumberET, press the calculateBtn button and then, check if the result set on resultTV is the same as if we use the class Calculator for it.

6. Checking the intents

The another interesting thing to test is testing if a certain intent has been triggered. This is done by getting the component from the intent as below:

@Test
public void checkIntentLaunched() {
    Button mLaunchCalculatorBtn = (Button) mLaunchActivity.findViewById(R.id.launch_calculator_btn);
    mLaunchCalculatorBtn.performClick();

    ShadowActivity shadowActivity = Shadows.shadowOf(mLaunchActivity);
    Intent startedIntent = shadowActivity.getNextStartedActivity();
    ShadowIntent shadowIntent = Shadows.shadowOf(startedIntent);

    assertThat(shadowIntent.getComponent())
            .isEqualTo(new ComponentName(mLaunchActivity, MainActivity.class));
}

The test press on the launch calculator button and then, check that the component of the intent which should start the next activity is the one which starts the Main activity.

7. Conclusions

Robolectric is a nice way to deal with unit testing in Android, since is shorts a lot the time needed to run the tests. If the lack of documentation is not a problem for you and you can deal with the time delay between when Google launch a new OS and Robolectric could "shadow" it, definitively it is a great tool to maintain the quality of the product.

No comments:

Post a Comment