To illustrate my latest problem with writing JUnit tests for my Android app, I wrote a simple example with two activities, StartActivityForResult
and ChildActivity
. The former contains a TextView
(for display purposes) and a Button
while the later contains just a Button
. The onClickListener
for the button in StartActivityForResult
simply starts an instance of ChildActivity
.
private View.OnClickListener onStart = new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d(TAG, "Start button clicked");
Intent intent = new Intent(StartActivityForResult.this, ChildActivity.class);
StartActivityForResult.this.startActivityForResult(intent, R.id.child_request);
}
};
Now I want to test this method using JUnit. So I wrote the following test:
package codeguru.startactivityforresult;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import junit.framework.Assert;
public class StartActivityForResultTest extends ActivityInstrumentationTestCase2<StartActivityForResult> {
public StartActivityForResultTest() {
super(StartActivityForResult.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
Log.d(TAG, "setUp()");
this.setActivityInitialTouchMode(false);
this.activity = this.getActivity();
this.resultText = (TextView) this.activity.findViewById(R.id.result_text);
this.startButton = (Button) this.activity.findViewById(R.id.start_button);
Intent data = new Intent();
data.putExtra(this.activity.getString(R.string.result), RESULT);
Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, data);
this.childMonitor = new Instrumentation.ActivityMonitor(ChildActivity.class.getName(), result, true);
this.getInstrumentation().addMonitor(this.childMonitor);
}
@Override
public void tearDown() throws Exception {
this.activity.finish();
super.tearDown();
}
@UiThreadTest
public void testStartButtonOnClick() {
Assert.assertTrue(this.startButton.performClick());
Activity childActivity = this.getInstrumentation().waitForMonitorWithTimeout(this.childMonitor, TIME_OUT);
Assert.assertNotNull(childActivity); // <------ Line 51
Button resultButton = (Button) childActivity.findViewById(R.id.result_button);
Assert.assertTrue(resultButton.performClick());
Assert.assertEquals(Integer.toString(RESULT), this.resultText.getText().toString());
}
private Activity activity = null;
private TextView resultText = null;
private Button startButton = null;
private Instrumentation.ActivityMonitor childMonitor = null;
private static final int TIME_OUT = 5 * 1000; // 5 seconds
private static final int RESULT = 69;
private static final String TAG = StartActivityForResultTest.class.getName();
}
Running this test gives the following output:
codeguru@trolloc:~/src/java/stackoverflow/sscce/StartActivityForResult/test$ adb logcat -c
codeguru@trolloc:~/src/java/stackoverflow/sscce/StartActivityForResult/test$ adb shell am instrument -w -e class codeguru.startactivityforresult.StartActivityForResultTest codeguru.startactivityforresult.tests/android.test.InstrumentationTestRunner
codeguru.startactivityforresult.StartActivityForResultTest:
Failure in testStartButtonOnClick:
junit.framework.AssertionFailedError
at codeguru.startactivityforresult.StartActivityForResultTest.testStartButtonOnClick(StartActivityForResultTest.java:51)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:214)
at android.test.InstrumentationTestCase.access$000(InstrumentationTestCase.java:36)
at android.test.InstrumentationTestCase$2.run(InstrumentationTestCase.java:189)
at android.app.Instrumentation$SyncRunnable.run(Instrumentation.java:1602)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4745)
at java.lang.reflect.Method.invokeNative(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)
Test results for InstrumentationTestRunner=.F
Time: 3.248
FAILURES!!!
Tests run: 1, Failures: 1, Errors: 0
codeguru@trolloc:~/src/java/stackoverflow/sscce/StartActivityForResult/test$ adb logcat -d codeguru.startactivityforresult.StartActivityForResultTest:D codeguru.startactivityforresult.StartActivityForResult:D codeguru.startactivityforresult.ChildActivity:D *:S
D/codeguru.startactivityforresult.StartActivityForResultTest( 954): setUp()
D/codeguru.startactivityforresult.StartActivityForResult( 954): onCreate()
D/codeguru.startactivityforresult.StartActivityForResult( 954): Start button clicked
layne@trolloc:~/src/java/stackoverflow/sscce/StartActivityForResult/test$
As far as I can tell, calling startButton.performClick()
doesn't start an instance of ChildActivity
. What gives?
As illustrated in an answer to one of my related questions, the problem is that I am calling waitForMonitorWithTimeout()
on the UI Thread. After realizing this, it certainly makes complete sense because waitForMonitorWithTimeout()
waits for the UI thread to complete some action (namely displaying an Activity's UI). However, by calling it on the UI thread, I am delaying that action from occuring.
来源:https://stackoverflow.com/questions/13105202/call-waitformonitorwithtimeout-from-a-uithreadtest