How can I create Android JUnit test case which tests the content of an Intent generated within an Activity?
I've got an Activity that contains a EditText window, and when the user has finished entering the required data, the Activity launches an Intent to an IntentService which records the data and continues with the application process. Here is the class I want to test, the OnEditorActionListener/PasscodeEditorListener is created as a separate class:
public class PasscodeActivity extends BaseActivity {
EditText m_textEntry = null;
PasscodeEditorListener m_passcodeEditorListener = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.passcode_activity);
m_passcodeEditorListener = new PasscodeEditorListener();
m_textEntry = (EditText) findViewById(R.id.passcode_activity_edit_text);
m_textEntry.setTag(this);
m_textEntry.setOnEditorActionListener(m_passcodeEditorListener);
}
@Override
protected void onPause() {
super.onPause();
/*
* If we're covered for any reason during the passcode entry,
* exit the activity AND the application...
*/
Intent finishApp = new Intent(this, CoreService.class);
finishApp.setAction(AppConstants.INTENT_ACTION_ACTIVITY_REQUESTS_SERVICE_STOP);
startService(finishApp);
finish();
}
}
class PasscodeEditorListener implements OnEditorActionListener{
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
PasscodeActivity activity = (PasscodeActivity) v.getTag();
boolean imeSaysGo = ((actionId & EditorInfo.IME_ACTION_DONE)!=0)?true:false;
boolean keycodeSaysGo = ((null != event) &&
(KeyEvent.ACTION_DOWN == event.getAction()) &&
(event.getKeyCode() == KeyEvent.KEYCODE_ENTER))?true:false;
if (imeSaysGo || keycodeSaysGo){
CharSequence seq = v.getText();
Intent guidEntry = new Intent(activity, CoreService.class);
guidEntry.setAction(AppConstants.INTENT_ACTION_PASSCODE_INPUT);
guidEntry.putExtra(AppConstants.EXTRA_KEY_GUID, seq.toString());
activity.startService(guidEntry);
return true;
}
return false;
}
}
How can I intercept the two possible outbound Intents generated by the activity and verify their contents?
Thanks
I figured how to use ContextWrapper with the help of another website.
Use ContextWrapper and override all of the intent functions. Generalizing for all of my Activity tests, I extended the ActivityUnitTestCase class and implemented the solution as a shim. Enjoy:
import android.app.Activity;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.test.ActivityUnitTestCase;
public class IntentCatchingActivityUnitTestCase<T extends Activity> extends ActivityUnitTestCase<T> {
protected Activity m_activity;
protected Instrumentation m_inst;
protected Intent[] m_caughtIntents;
protected IntentCatchingContext m_contextWrapper;
protected class IntentCatchingContext extends ContextWrapper {
public IntentCatchingContext(Context base) {
super(base);
}
@Override
public ComponentName startService(Intent service) {
m_caughtIntents = new Intent[] { service };
return service.getComponent();
}
@Override
public void startActivities(Intent[] intents) {
m_caughtIntents = intents;
super.startActivities(intents);
}
@Override
public void startActivity(Intent intent) {
m_caughtIntents = new Intent[] { intent };
super.startActivity(intent);
}
@Override
public boolean stopService(Intent intent) {
m_caughtIntents = new Intent[] { intent };
return super.stopService(intent);
}
}
// --//
public IntentCatchingActivityUnitTestCase(Class<T> activityClass) {
super(activityClass);
}
protected void setUp() throws Exception {
super.setUp();
m_contextWrapper = new IntentCatchingContext(getInstrumentation().getTargetContext());
setActivityContext(m_contextWrapper);
startActivity(new Intent(), null, null);
m_inst = getInstrumentation();
m_activity = getActivity();
}
protected void tearDown() throws Exception {
super.tearDown();
}
}
Alternatively, you could re-factor your code in order to do "clean" unit-test (I mean a unit test that has everything mocked out except the class under test). Actually, I have a situation myself, where I get a java.lang.RuntimeException: Stub!
because the code I want to unit test creates new Intents containing mocks that I have injected.
I consider creating my own factory for intents. Then I could inject a mocked out factory to my class-under-test:
public class MyClassToBeTested {
public MyClassToBeTested(IntentFactory intentFactory) {
//assign intentFactory to field
}
....
public void myMethodToTestUsingIntents() {
Intent i = intentFactory.create();
i.setAction(AppConstants.INTENT_ACTION_PASSCODE_INPUT);
//when doing unit test, inject a mocked version of the
//IntentFactory and do the necessary verification afterwards.
....
}
}
My situation is not the same as yours, but I believe you could apply a factory-pattern to solve it as well. I prefer to write code to support true unit tests, but must admit that your solution is quite clever.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With