Tuesday, October 23, 2012

Simple test for AlertDialog using Robolectric

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

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);
          confirmNewPassword = (EditText)
                findViewById(id.change_password_new_confirm_et);
          changePasswordButton = (Button)
                findViewById(id.change_password_btn);
     }
     //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. 

6 comments:

  1. while following this steps, my eclipse is giving a red underline below shadowOf. I have imported org.Roboelectric

    ReplyDelete
  2. Though this is very hard to implement this using the basic android framework, but if it is possible over there, please suggest something so that i need not use some third party library

    ReplyDelete
  3. Anshu,
    use Robolectric.shadowOf
    INSTEAD OF
    shadowOf

    ReplyDelete
    Replies
    1. You are right. I have a static import of Robolectric. For better clarity, I've made an edit to the post.

      Delete