Wednesday, December 22, 2010

Testing startActivityForResult() and onActivityResult()

Android provides a simple way to invoke an activity and receive a result back from it when it exits using Activity.startActivityForResult(). As of Robolectric 0.9.5, it's easy to put this under test.

Let's say you have one activity which displays the user's name, and a second activity which asks the user for their name:

class NameShowingActivity extends Activity {
  private static final int NAME_RESPONSE = 1;
 
  TextView nameView;
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    nameView = new TextView(this);
  }
 
  void askForUserName() {
    startActivityForResult(
        new Intent(this, NameChooserActivity.class), NAME_RESPONSE);
  }
 
  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == NAME_RESPONSE) {
      if (resultCode == RESULT_OK) {
        nameView.setText(data.getStringExtra("name"));
      } else if (resultCode == RESULT_CANCELED) {
        nameView.setText("No name given.");
      }
    }
  }
}

Let's test it! The askForName() method launches our name chooser activity, which will eventually respond with the entered name. We want to ensure that the name view has been updated with that name:

@RunWith(RobolectricTestRunner.class)
public class NameShowingActivityTest {
 
  private NameShowingActivity nameShowingActivity;
 
  @Before
  public void setUp() throws Exception {
    nameShowingActivity = new NameShowingActivity();
    nameShowingActivity.onCreate(null);
  }
 
  @Test
  public void shouldFillNameViewFromChooserResponse() throws Exception {
    nameShowingActivity.askForUserName();
 
    shadowOf(nameShowingActivity).receiveResult(
        new Intent(nameShowingActivity, NameChooserActivity.class),
        Activity.RESULT_OK,
        new Intent().putExtra("name", "Joe"));
 
    assertEquals("Joe", nameShowingActivity.nameView.getText());
  }
}

When you call receiveResult() on your activity's shadow, Robolectric notices that the activity is listening for a response from the NameChooserActivity, so it passes the result intent (the second one) to your activity's onActivityResult() method.

But we're not testing the RESULT_CANCELLED branch of our code. Let's test that too:

public void shouldDisplayErrorMessageInNameViewWhenUserCancels() throws Exception {
    nameShowingActivity.askForUserName();
 
    shadowOf(nameShowingActivity).receiveResult(
        new Intent(nameShowingActivity, NameChooserActivity.class),
        Activity.RESULT_CANCELED,
        null);
 
    assertEquals("No name given.", nameShowingActivity.nameView.getText());
  }

2 comments:

  1. Thank you for this post, very helpful!

    Just some note for those who get the following error:

    java.lang.RuntimeException: No intent matches Intent{componentName=ComponentName{pkg='ru.alvik.cafe', cls='ru.alvik.cafe.PaymentActivity'}} among []
    at com.xtremelabs.robolectric.shadows.ShadowActivity.receiveResult(ShadowActivity.java:365)
    at ru.alvik.cafe.CafeActivityTest.testClearOrderAfterPaymentCompleted(CafeActivityTest.java:79)


    You have probably just started the activity rather than starting it for result.

    ReplyDelete
  2. Hello I am facing this error while calling the startActivityForResult(intent, 0)

    java.lang.RuntimeException: No intent matches Intent{componentName=ComponentName{pkg='com.android.test', cls='com.android.CaptureActivity'}, extras=null} among [Intent{componentName=ComponentName{pkg='com.android.test', cls='com.android.CaptureActivity'}, extras=null}]

    Here is how I am calling:
    Intent intent = new Intent(ActivityDetail.this, CaptureActivity.class);
    intent.putExtra("FromScreen", 1);
    intent.putExtra("BUTTON_CLICK_STATE",false);
    startActivityForResult(intent, 0);

    Please , let me know what I am doing wrong
    Thanks

    ReplyDelete