EDIT (10/30/2014): All references to the method shadowOf() is actually a reference to Robolectric.shadowOf(). I have Robolectric saved as a favorite in Eclipse, which automatically creates a static import for it.
---
Anyone who has tried to use the Android testing framework knows it can be painful, mostly because of the slowness of feedback loop and the setup required to run tests. Robolectric eases this pain. If you're writing an Android application and haven't discovered Robolectric, do yourself a favor. First, it makes it possible to run unit tests without needing to connect an Android device or an emulator. Read more about it here.
---
Anyone who has tried to use the Android testing framework knows it can be painful, mostly because of the slowness of feedback loop and the setup required to run tests. Robolectric eases this pain. If you're writing an Android application and haven't discovered Robolectric, do yourself a favor. First, it makes it possible to run unit tests without needing to connect an Android device or an emulator. Read more about it here.
Ever tried to test the appearance of a
dialog on screen? The Android testing framework makes it hard.
Robolectric makes it easy.
Lets test the apperance of a dialog on
screen with the help of a simple sample activity.
This activity, cleverly called
ChangePasswordActivity, helps a user change his password. The
activity has a field to enter a new password and a second field to
confirm the new password entered. Once satisfied the user can click
the 'Change Password' button to send the changes to the database.
For those of you that might have
trouble visualizing what such an activity might look like, see below.
Below are the field
declarations.
public
class
ChangePasswordActivity
extends
BaseActivity
{
private
EditText
newPassword;
private
Button
changePasswordButton;
private
EditText
confirmNewPassword;
//some
code here
protected
void
inflateViewObjects()
{
newPassword
=
(EditText)
findViewById(id.change_password_new_et);
findViewById(id.change_password_new_et);
confirmNewPassword
=
(EditText)
findViewById(id.change_password_new_confirm_et);
findViewById(id.change_password_new_confirm_et);
changePasswordButton
= (Button)
findViewById(id.change_password_btn);
findViewById(id.change_password_btn);
}
//some code here
}
//some code here
}
Next
let's look at the code that gets invoked when a user pushes the
'Change Password' button.
void
handleSubmitNewPasswordClick()
{
if
(meetsPasswordChangeCriteria())
{
sendPasswordChangeToDatabase();
showConfirmationToast();
finishActivity();
}
}
Before the new password is sent to the
database, it must meet certain criteria. For instance, both of the
fields should contain text, and that text should match. If any of
that criteria is not met, the password is not persisted.
Let's look at the
meetsPasswordChangeCriteria() method.
private
boolean
meetsPasswordChangeCriteria()
{
boolean
criteriaMet =
true;
if
(passwordFieldBlank())
{
criteriaMet
= false;
createPasswordFieldsBlankDialog();
}
if
(passwordFieldsMatch()
&&
criteriaMet) {
criteriaMet
= false;
createPasswordsNotMatchingDialog();
}
return
criteriaMet;
}
Here
we see a couple of conditions are tested. If a condition is not met, a
dialog is created informing the user of the status of the password
change. We'll look at the first condition.
The
passwordFieldIsBlank() method helps set the flag that tells the calling
method, in this case handleSubmitNewPasswordClick(), whether or not
the criteria was met. We'll assume in this case it was not. That
means the passwordFieldIsBlank() method returns true.
Lets
have a look at that simple method.
private
boolean
passwordFieldBlank()
{
return
newPassword.getText().toString().equals("")
||
confirmNewPassword.getText().toString().equals("");
}
Pretty
basic. Check to see if the two fields have any text. If one does not, then this method returns true, which, in the previous
method, meetsPasswordChangeCriteria(), causes the criteriaMet flag to be set to false before
creating a dialog that prompts the user that there is some missing
information. The screen then looks like the one below.
So
finally we get to the reason for this post. How do we create a unit
test that checks to see if this dialog appears?
If
you're reading this test case, you're probably new to Robolectric.
Well, that's fine, I am too. But the following method is an example of a typical
test method in Robolectric and hopefully you're past the point of
setting up your Robolectric test project.
What
we're going to do with this test is create an activity, ensure the
newPassword and confirmPassword fields are blank and then we're going
to programatically push the 'Change Password' button. Then its up to
Robolectric – and its beautiful concept of shadow classes. Let's
look at the code and then I'll talk about it.
public
void
itShowsDialogIfAFieldIsBlank()
{
setUpActivity();
setEditText(R.id.update_email_edittext,
"");
setEditText(R.id.update_username_edittext,
"");
pushUpdateButton();
AlertDialog
alert =
ShadowAlertDialog.getLatestAlertDialog();
ShadowAlertDialog
sAlert =
shadowOf(alert);
assertThat(sAlert.getTitle().toString(),
equalTo(activity.getString(R.string.all_fields_required_)));
}
The
setUpActivity() method encapsulates the setup needed for
instantiating an activity. Right now, think of it as a black box.
Just know that after that method is called, we have a valid instance
of the ChangePasswordActivity class.
The
method setEditText() passes the id of the EditText field we wish to
set the text of and then a string value of the text we wish to be
set. In this case it's blank. The pushUpdateButton() encapsulates
pushing the 'Confirm Password' button. That is just a matter of
calling findViewById() to locate the button element and then calling
performClick on it.
Once
that's done we know that our activity should display a dialog like
the one in the last photo. But how do know. We use the static method
getLatestAlertDialog() of the ShadowAlertDialog class which looks at
the current activity and finds the id of the last dialog that was
show. The activity class holds on to this information.
Once
we have that AlertDialog, we need to make some assertions about it.
Well it would be helpful to know things like the title that is shown
in the dialog. Normally the AlertDialog class doesn't expose a
property that would allow us to examine the title of the dialog, but
if we look at the shadow implementation that Robolectric created, we
can get access to properties that might be useful to test. In this
case, we're asserting that last dialog has a title that says “All
fields required”. Actually here that string value is held in the
string resource file. That way if it ever needs changed, we only need
to change it in one place.
In this assert method we're using the equalTo() method that comes part of the hamcrest core matchers library. You'll have to import org.hamcrest.CoreMatchers. I'm doing a static import. I could have just as easily used assertEquals(string1, string2), but I find that the CoreMatchers library offers some advantages in readability. We can dive into that another time.
The purpose here was to hopefully help someone test something that might have once seemed
untestable.

