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.

Tuesday, 10 March 2015

Unit testing with Robolectric, Mockito and AssertJ on Android Studio

Introduction

Unit testing is an important task for any project, specially now with the grow number of Startups, where the projects should pivot very fast ensuring the existence feature remains usable at the same time.

In this post I will try to explain how to integrate Robolectric with Mockito and AssertJ as help, from the first step and with the sample project stored on GitHub.

To do so, I start with a simple app which calculates the sum of two numbers. You can find it in this branch:

https://github.com/jiahaoliuliu/unitTestingWithAndroidStudio/tree/creatingApp

Installation

We start with the installation of Robolectric, followed by Mockito and then, finally, with AssertJ. To make the code much more cleaner, it is good idea to create first a separated module.

1 Creating separated module

1.1 Click on File -> New module
1.2 Select Java Library and click on Next
1.3 Call it "app-test" and click on Finish

Android studio will take a while to recompile the project and then, a new project called app-test will be created with the follow structure:


The new module comes with the space for the source code but not for the test code. So, we must create one manually.

1.4 Right click on the src folder and create a new directory called test
1.5 Right click on the new folder and create a new directory called java.
1.6 Right click on the java folder and create a new package called "com.jiahaoliuliu.unittestingwithandroidstudio".

This is the final structure:



Note that the name of the package matches with the name of the package on the app module. This will save many imports.

1.7 Open the build.gradle file in the new module and add the follow code to it, at the top, before apply the plugin of java

evaluationDependsOn(":app")

You can see the code on the branch separatedModule:
https://github.com/jiahaoliuliu/unitTestingWithAndroidStudio/tree/separatedModule

1.8 (optional)
The folder lib is not going to be used, so it can be removed also.
Once it is done, the sentence on build.gradle related with this folder could also be removed.

This is how it is going to be look like:

evaluationDependsOn(":app")

apply plugin: 'java'

dependencies {
}

2 Integrating Robolectric

The next step is integrate Robolectric. At the time of writing, Robolectric 2.4 is stable, but 3.0-Snapshot seems to have less errors, so we are going to use robolectric 3.0.

2.1 On the build.gradle of the general project, add the follow line on both build script and all projects, just under jcenter():

maven { url "https://oss.sonatype.org/content/repositories/snapshots" }

2.2 Sync the project
2.3 At the build.gradle of the new module, add dependencies and extra task configuration related with Robolectric inside of the dependencies block:

dependencies {
    def androidModule = project(':app')
    compile project(path: ':app', configuration: 'debugCompile')

    def debugVariant = androidModule.android.applicationVariants.find({it.name == 'debug'})
    compile debugVariant.javaCompile.classpath
    compile debugVariant.javaCompile.outputs.files
    compile files(androidModule.plugins.findPlugin("com.android.application").getBootClasspath())

    compile 'org.robolectric:robolectric:3.0-SNAPSHOT'
    compile 'junit:junit:4.11'
}

tasks.withType(Test) {
    scanForTestClasses = false
    include "**/*Should.class"
    include "**/*Test.class"
    include "**/*Tests.class"
    exclude "**/*IT.class"

}

2.4 Sync the project.

The actual configuration works with the version 1.0.0 of gradle as android tools. If you see the follow error after sync:

Error:(12, 0) No signature of method: com.android.build.gradle.AppPlugin.getBootClasspath() is applicable for argument types: () values: []

Check on the version of gradle used on the main build.gradle file. It should be as follow:

classpath 'com.android.tools.build:gradle:1.0.0'


2.5 Override your own test runner
2.5.1 Go to the src/main/java folder of the new module and remove the class MyClass if exits.
2.5.2 Right click on the package and select New -> Java class. Name it as RobolectricGradleTestRunner.
2.5.3 Copy the code in this branch:
https://github.com/jiahaoliuliu/unitTestingWithAndroidStudio/blob/robolectricIntegration/app-test/src/main/java/com/jiahaoliuliu/unittestingwithandroidstudio/RobolectricGradleTestRunner.java



2.6 Create new test module to check it works.
2.6.1 Go to the folder test/java of the new module and right click on the package.
2.6.2 Select New -> Java class
2.6.3 Rename it as MainActivityTest
2.6.4 The first thing to do is set that it should run with the customised test runner instead of the official one. To do so, add the follow line between the import statement and the declaration of the class:

@RunWith(RobolectricGradleTestRunner.class)

2.6.5 Once it is done, copy the follow code in the test.

The above code could be find here:
https://github.com/jiahaoliuliu/unitTestingWithAndroidStudio/blob/robolectricIntegration/app-test/src/test/java/com/jiahaoliuliu/unittestingwithandroidstudio/MainActivityTest.java

The code of this step could be find in the branch robolectricIntegration of the testing project:
https://github.com/jiahaoliuliu/unitTestingWithAndroidStudio/tree/robolectricIntegration

2.7 (Just in case) Remove old test data
By default, when Android studio creates a new project, it also created a folder called "androidTest" with a class called ApplicationTest in it. This class will require the emulator or the physical device connected to the computer to be run, which is totally against the purpose of using Robolectric.

The easiest way to fix this problem is removing it totally.

3. Integrating Mockito

Once Robolectric is integrated, it is easy to integrate Mockito.
3.1 Goes to the build.gradle file of the module app-test and add the follow line in the dependencies block, just below to the junit test line:

testCompile "org.mockito:mockito-core:1.+"

3.2 Goes to the new module and right click on the package of test/java, select New -> Java class.
3.3 Name it as calculatorTest and copy and paste the follow code:



Which could be find here:

The source code of this branch could be found in the branch mockitoIntegration of the project:

4. AssertJ integration

AssertJ is a tool provided by Square which make the tests a bit more beautiful. To integrate it to the project, do the follow:
4.1 On the build.gradle of the "app" project, add the follow line in the block of dependencies. Repeat, in the module "app", not "app-test"

compile 'com.squareup.assertj:assertj-android:1.0.0'

4.2 Sync

To test it, go to the test MainActivityTest and put the follow line on the simpleTest:

assertThat(mMainActivity).isNotNull();

You must put the follow import to make it works:

import static org.assertj.core.api.Assertions.assertThat;

There are much more things that assertJ could help. Check its official web page:

The code of this step could be find in the branch assertJIntegration of the project:

Conclusion

So far seems easy to integrate Robolectric, Mockito and AssertJ into the Android Studio. Now it is when the fun begins with the unit tests.





Thursday, 26 February 2015

Monkey Talk integration with Android Studio

Monkey talk is an excellent tool to run feature tests, which simulates the interaction of the user with the application.

Unfortunately it does not has good integration with Android Studio, which reduces the among of users. In this tutorial I am going to show how to do it on more manual way, with a simple project.

1. Add the AspectJ plugin
1.1 Go to the main build.gradle file and add the follow one, inside the dependencies, like this.

classpath 'com.uphyca.gradle:gradle-android-aspectj-plugin:0.9.5'


1.2 In the build.gradle file of the module "app" add the follow line at the top, like this:

apply plugin: 'android-aspectj'


2. Add Monkey talk library
2.1 Download the latest version of MonkeyTalk Agent from their web page. It is contained inside the IDEs.
2.2 Inside of the module app, create a new folder called monkey-libs and paste the Monkey Talk jar library inside.
2.3 Open the build.gradle file of the app and paste the follow lines.
2.3.1 Paste the follow code inside of the buildTypes:

        monkeytalk.initWith(buildTypes.debug)
        monkeytalk {
            applicationIdSuffix ".monkey"
        }

2.3.2 Paste the follow code inside of the dependencies:

monkeytalkCompile fileTree(dir: 'monkey-libs', include: ['*.jar'])

Here is the example:

3. Include the permissions
Just in case you have not added them, add the required permissions to the Manifest file.
3.1 Open the Android Manifest file.
3.2 Copy the follow line into the manifest file. This will adds the Get task and internet permissions.

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_TASKS" />


4. Run the app
4.1 To run the app, open the Terminal or the console and type the follow:

./gradlew clean installMonkeytalk

This command will compile the app and install it into the existence device connected.

Note1: The method above does not works with the version 1.1.0 of the android gradle tool, but the version 1.0.0.

Note2: I have tested the method above with simple projects but with big projects actually it fails. So, good luck!

Source code:

Source:

Monday, 23 February 2015

Android Robolectric - Solving multidex problem

One of the biggest advantages I see with Android Studio, is it is very easy to deal with the problem when there are more than 65K methods in the apk, usually because too many libraries.

But this causes problems with Robolectric, which results in the follow error:

java.lang.RuntimeException: java.lang.RuntimeException: Multi dex installation failed (/Users/jiahaoliuliu/..../ (Is a directory)).

I have detected this both on Robolectric 2.4 and 3.0. To fix this, do the follow workaround:

1. Create a new Java class which extends the class Application, if you have not done it yet. It could be called MyApplication.

2. Go to the Android manifest, and add the follow line just below application tag as attribute:

<application
    android:name=".MyApplication"
    ... >
    ...
</application>


3. Open the new class called MyApplication and override the method attachBaseContext as follow:

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        try {
            MultiDex.install(this);
        } catch (RuntimeException multiDexException) {
            // Work around Robolectric causing multi dex installation to fail, see
            // https://code.google.com/p/android/issues/detail?id=82007.
            boolean isUnderUnitTest;

            try {
                Class<?> robolectric = Class.forName("org.robolectric.Robolectric");
                isUnderUnitTest = (robolectric != null);
            } catch (ClassNotFoundException e) {
                isUnderUnitTest = false;
            }

            if (!isUnderUnitTest) {
                // Re-throw if this does not seem to be triggered by Robolectric.
                throw multiDexException;
            }
        }
    }

Source:

Thursday, 5 February 2015

Programming as art

Signing GitHub with the keychain from Mac OS X

Mac OS X comes with a feature called keychain, which allow the user to save the password into it and then, authenticate automatically.

This also works with any projects on GitHub, but it need a bit of hand work.

1. Go to your repository folder
2. Open a folder called .git
3. Open the file called config
4. Modify the protocol used for origin to ssh and add the git user to it.

e.g. this is the original code for a simple project on GitHub

[remote "origin"]
        url = https://github.com/jiahaoliuliu/MockitoTest.git
        fetch = +refs/heads/*:refs/remotes/origin/*

And this is the final result
[remote "origin"]
        url = ssh://git@github.com/jiahaoliuliu/MockitoTest.git
        fetch = +refs/heads/*:refs/remotes/origin/*

Tuesday, 27 January 2015

Running Dia on Mac OS X Yosemite

Dia is an excellence tool to draw UML diagrams. Originally it was built for Linux. Later, it is also exported to Windows and Mac OS X.

It could be downloaded from here:
http://dia-installer.de/download/index.html

For some strange reason, it cannot be run normally on Mac OS X Yosemite. To run it, you should run the follow command in the terminal:


DISPLAY=:0 /Applications/Dia.app/Contents/Resources/bin/dia

The another way to run it is set the command DISPLAY=:0 in the running file. Here are the steps:
1. Go to the Application directory
2. Right click on Dia and select "Show package contents"
3. Go to Contents -> Resources -> bin
4. Open dia file with an text editor
5. Remove the block under # Check for X11 and just leave the line which says:

export Display=:0

It will be something like this:

#export PATH="/usr/texbin:/opt/local/bin:/sw/bin/:/Library/Frameworks/Python.framework/Versions/Current/bin:/usr/local/bin:$CWD:$PATH"

# Check for X11
export DISPLAY=:0

startx=`which startx`
if [[ "" == $startx ]]; then
 if [[ ! -e /opt/X11/bin/startx ]]; then
   osascript -e 'tell app "System Events" to display dialog "X11 (XQuartz) is not installed or not running. Would you like to visit xquartz.macosforge.org now in order to download and install XQuartz?" with icon 0'
   if [[ $? -eq 0 ]]; then
     open http://xquartz.macosforge.org
     exit
   fi
  fi
fi

Source:
Technical Blog - Dia Diagram Mac OSX Yosemite Fix