Android Unit Test: ActivityMonitor waitForActivityWithTimeout returns NULL, getActivity never returns, permission error INJECT_EVENTS

I use the Android testing platform in accordance with the recommendations of Google: ActivityInstrumentationTestCase2. I experienced the following errors during test runs of RANDOM , but with constant fatality. This means that sometimes all tests passed (happy!), But many times it happened by accident with any of these three errors. This upsets and makes me distrust the test results.

To describe the problems in detail, I provided a simplified pseudocode and three problems below. Both test cases are independent of each other.

public class FirstActivityTest extends ActivityInstrumentationTestCase2<FirstActivity> { private FirstActivity mActivity; private ActivityMonitor mActivityMonitor; public FirstActivityTest () { super(FirstActivity.class); } public void setUp() throws Exception { super.setUp(); setActivityInitialTouchMode(false); mActivity = getActivity(); assertNotNull("Cannot start test since target Activity is NULL!", mActivity); mActivityMonitor = getInstrumentation().addMonitor(SecondActivity.class.getName(), null, false); } public void tearDown() throws Exception { super.tearDown(); if(mActivity != null) { mActivity.finish(); mActivity = null; } if(mActivityMonitor != null) { getInstrumentation().removeMonitor(mActivityMonitor); mActivityMonitor = null; } } /** * Open FirstActivity, enter a text and click submit button. * Verifies SecondActivity is open. */ public void testA_HappyPath() { Activity secondActivity = null; try { //(Omitted) Get edit text and enter a valid value //(Omitted) Find submitButton view //Click submit button TouchUtils.clickView(this, submitButton); //Wait for result and validate: secondActivity = mActivityMonitor.waitForActivityWithTimeout(10000); assertNotNull("Result SecondActivity should NOT be null!", secondActivity ); } finally { //Clean up: if(secondActivity != null) { secondActivity .finish(); secondActivity = null; } } } /** * Open FirstActivity, do NOT enter a text and click submit button. * Verifies error message is returned. */ public void testB_SadPath() { //(Omitted) Find submitButton view //Click submit button TouchUtils.clickView(this, submitButton); //(Omitted) Validate error message is displayed } } 

Now I have run these two test cases again and again (they will be executed in alphabetical order), the following results:

  • Both test cases passed, OR
  • testA_HappyPath () failed because ActivityMonitor.waitForActivityWithTimeout () returned NULL SecondActivity. But when I looked at my device, SecondActivity is displayed correctly. Somehow, the test did not notice this. Why?
  • If testA_HappyPath () fails, the next test B_SadPath () will hang during setUp ()> getActivity () for an unlimited time. I think I closed everything in tearDown (). Why?
  • testB_SadPath () often did not work when using TouchUtils.clickView () with the following error: " INJECT_EVENTS permission is required to enter another application" (regardless of whether the test passes or does not pass testA_HappyPath). Why?

Any helpful feedback is appreciated. Thanks!


I reviewed these 3 questions over the course of a few days, studied many suggestions all over the Internet, and made several attempts and mistakes. However, not a single problem solved a specific problem, however, combining what I found, I solved problem (1) and (2) above, but still have problem (3) that is not resolved. Below is a detailed description of what I did to make this work.

ISSUE (1) ActivityMonitor.waitForActivityWithTimeout () returns NULL

1.1. Now I found out that I have to declare getInstrumentation (). AddMonitor () BEFORE getActivit (). See how I changed the setUp () method, and that somehow fixed the problem. Anyone who understands why this is a requirement, please let us know, we appreciate it.

1,2. In the emulator, this call could sometimes return NULL, which caused an error. I found out that this is because the wait times were too low. Thus, increasing the latency helps prevent the ActivityMonitor from returning too quickly.

ISSUE (2) Next testB_SadPath () will hang during setUp ()> getActivity () indefinitely

2.1. As I described above, this happened when the previous test (testA_HappyPath) failed. I thought my tearDown () cleared everything and checked the next test. It so happened that testA was expecting SecondActivity to appear on the screen, but since ActivityMonitor.waitForActivityWithTimeout () returned NULL, testA failed. TearDown () was executed just fine. The problem is that SecondActivity actually appeared on the screen, but it never stops in the finally block, because its instance of the "secondActivity" method was still null. The presence of SecondActivity live and lingers on the screen, causing the next getActivity () to hang. I fixed this by modifying the finally block to make sure that SecondActivity ever exists, it is disabled.

These changes are summarized in the code below (see setUp () and finally block).

 public class FirstActivityTest extends ActivityInstrumentationTestCase2<FirstActivity> { private FirstActivity mActivity; private ActivityMonitor mActivityMonitor; public FirstActivityTest () { super(FirstActivity.class); } public void setUp() throws Exception { super.setUp(); setActivityInitialTouchMode(false); } public void tearDown() throws Exception { super.tearDown(); if(mActivity != null) { mActivity.finish(); mActivity = null; } if(mActivityMonitor != null) { getInstrumentation().removeMonitor(mActivityMonitor); mActivityMonitor = null; } } /** * Open FirstActivity, enter a text and click submit button. * Verifies SecondActivity is open. */ public void testA_HappyPath() { mActivityMonitor = getInstrumentation().addMonitor(SecondActivity.class.getName(), null, false); mActivity = getActivity(); assertNotNull("Cannot start test since target Activity is NULL!", mActivity); Activity secondActivity = null; try { //(Omitted) Get edit text and enter a valid value //(Omitted) Find submitButton view //Click submit button TouchUtils.clickView(this, submitButton); //Wait for result and validate: secondActivity = mActivityMonitor.waitForActivityWithTimeout(20000); assertNotNull("Result SecondActivity should NOT be null!", secondActivity ); } finally { //Clean up: if(secondActivity == null) { //If empty, wait longer because need to shut down the foreground activity, if any: secondActivity = mActivityMonitor.waitForActivityWithTimeout(20000); } if(secondActivity != null) { secondActivity .finish(); secondActivity = null; } } } /** * Open FirstActivity, do NOT enter a text and click submit button. * Verifies error message is returned. */ public void testB_SadPath() { mActivity = getActivity(); assertNotNull("Cannot start test since target Activity is NULL!", mActivity); //(Omitted) Find submitButton view //Click submit button TouchUtils.clickView(this, submitButton); //(Omitted) Validate error message is displayed } } 

RESEARCH (3) testB_SadPath () often fails when using TouchUtils.clickView () with the following error: "INJECT_EVENTS permission is required for injection into another application"

I still can not solve this last problem: - (

+4
source share
1 answer

RESEARCH (3) testB_SadPath () often fails when using TouchUtils.clickView () with the following error: "INJECT_EVENTS permission is required for injection into another application"

I found an alternative way to avoid this problem on my Android Unit Test. Instead of using TouchUtils.clickView (), I directly perform the click operation on the button itself, calling the performClick () function. The following modified test code resolves my random INJECT_EVENTS permission error. In particular, see PopulateDataAndClickSubmit ().

 /** * Open FirstActivity, enter a text and click submit button. * Verifies SecondActivity is open. */ public void testA_HappyPath() { mActivityMonitor = getInstrumentation().addMonitor(SecondActivity.class.getName(), null, false); mActivity = getActivity(); assertNotNull("Cannot start test since target Activity is NULL!", mActivity); Activity secondActivity = null; try { String dataValue = "MyNameIsNoLongerFooNorBar"; populateDataAndClickSubmit(dataValue); //Wait for result and validate: secondActivity = mActivityMonitor.waitForActivityWithTimeout(20000); assertNotNull("Result SecondActivity should NOT be null!", secondActivity ); } finally { //Clean up: if(secondActivity == null) { //If empty, wait longer because need to shut down the foreground activity, if any: secondActivity = mActivityMonitor.waitForActivityWithTimeout(20000); } if(secondActivity != null) { secondActivity.finish(); secondActivity = null; } } } /** * Open FirstActivity, do NOT enter a text and click submit button. * Verifies error message is returned. */ public void testB_SadPath() { mActivity = getActivity(); assertNotNull("Cannot start test since target Activity is NULL!", mActivity); String dataValue = null; populateDataAndClickSubmit(dataValue); //(Omitted) Validate error message is displayed } private void populateDataAndClickSubmit(final String dataValueString) { final EditText editDataView = //(omitted) find it from the activity layout final Button submitButton = //(Omitted) Find submitButton view mActivity.runOnUiThread( new Runnable() { public void run() { editDataView.setText(dataValueString); submitButton.performClick(); } } ); //Wait and allow app to be idle while performClick to finish and activity re-drawn: getInstrumentation().waitForIdleSync(); } 

NOTES:

  • This solution is not an answer to why touchUtils.clickView () throws a random event permissions error for events
  • The View.performClick () function requires your activity to have a view in the question that implements OnClickListener (). In my case, SubmitButton already has it so that it is a convenience code change.
  • The getInstrumentation () function waitForIdleSync () allows the test code to remain idle until the application completes its work and returns the correct layout. The same line is executed in touchUtils.clickView () if you look at its java code.
+1
source

All Articles