Skip to content

Instantly share code, notes, and snippets.

@drewhannay
Last active June 21, 2017 18:25
Show Gist options
  • Save drewhannay/7fa758847cad8a6dc26f0d1d3cb068ad to your computer and use it in GitHub Desktop.
Save drewhannay/7fa758847cad8a6dc26f0d1d3cb068ad to your computer and use it in GitHub Desktop.
Android Espresso Activity Clean Up
/**
* Copyright (C) 2017 Drew Hannay
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.test.rule;
import android.app.Activity;
import android.support.test.runner.MonitoringInstrumentationAccessor;
import android.util.Log;
import java.lang.reflect.Field;
/**
* Subclass of {@link ActivityTestRule} that cleanly handles finishing multiple activities.
* <p/>
* The official ActivityTestRule only calls finish() on the initial activity. However, this can cause problems if the
* test ends in a different activity than which it was started. In this implementation, we call finish() on all
* Activity classes that are started and wait until they actually finish before proceeding.
*/
public class FinishingActivityTestRule<T extends Activity> extends ActivityTestRule<T> {
private static final String TAG = FinishingActivityTestRule.class.getSimpleName();
public FinishingActivityTestRule(Class<T> activityClass) {
super(activityClass);
}
public FinishingActivityTestRule(Class<T> activityClass, boolean initialTouchMode) {
super(activityClass, initialTouchMode);
}
public FinishingActivityTestRule(Class<T> activityClass, boolean initialTouchMode, boolean launchActivity) {
super(activityClass, initialTouchMode, launchActivity);
}
@Override
void finishActivity() {
MonitoringInstrumentationAccessor.finishAllActivities();
// purposefully don't call super since we've already finished all the activities
// instead, null out the mActivity field in the parent class using reflection
try {
Field activityField = ActivityTestRule.class.getDeclaredField("mActivity");
activityField.setAccessible(true);
activityField.set(this, null);
} catch (NoSuchFieldException e) {
Log.e(TAG, "Unable to get field through reflection", e);
} catch (IllegalAccessException e) {
Log.e(TAG, "Unable to get access field through reflection", e);
}
}
}
/**
* Copyright (C) 2017 Drew Hannay
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.test.rule;
import android.app.Activity;
import android.support.test.espresso.intent.Intents;
/**
* Subclass of {@link FinishingActivityTestRule} that can be used in place of
* {@link android.support.test.espresso.intent.rule.IntentsTestRule} and still get the bug fix provided by
* FinishingActivityTestRule
*/
public class FinishingIntentsTestRule<T extends Activity> extends FinishingActivityTestRule<T> {
private boolean isLoaded;
public FinishingActivityTestRule(Class<T> activityClass) {
super(activityClass);
}
public FinishingActivityTestRule(Class<T> activityClass, boolean initialTouchMode) {
super(activityClass, initialTouchMode);
}
public FinishingActivityTestRule(Class<T> activityClass, boolean initialTouchMode, boolean launchActivity) {
super(activityClass, initialTouchMode, launchActivity);
}
protected void afterActivityLaunched() {
Intents.init();
isLoaded = true;
super.afterActivityLaunched();
}
protected void afterActivityFinished() {
super.afterActivityFinished();
if (isLoaded) {
// release() will NPE if init() wasn't called
// (usually happens if a test failed before the activity was launched)
Intents.release();
isLoaded = false;
}
}
}
/**
* Copyright (C) 2017 Drew Hannay
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.test.runner;
import android.os.Handler;
import android.os.Looper;
import android.support.test.InstrumentationRegistry;
import android.util.Log;
/**
* Helper class for calling protected methods in MonitoringInstrumentation
*/
public class MonitoringInstrumentationAccessor {
private static final String TAG = MonitoringInstrumentationAccessor.class.getSimpleName();
private static final Handler HANDLER_FOR_MAIN_LOOPER = new Handler(Looper.getMainLooper());
private MonitoringInstrumentationAccessor() {}
/**
* Synchronously finish all currently started activities
*/
public static void finishAllActivities() {
MonitoringInstrumentation instrumentation = (MonitoringInstrumentation) InstrumentationRegistry.getInstrumentation();
MonitoringInstrumentation.ActivityFinisher activityFinisher = instrumentation.new ActivityFinisher();
HANDLER_FOR_MAIN_LOOPER.post(activityFinisher);
long startTime = System.currentTimeMillis();
instrumentation.waitForActivitiesToComplete();
long endTime = System.currentTimeMillis();
Log.i(TAG, String.format("waitForActivitiesToComplete() took: %sms", endTime - startTime));
}
}
@mrPjer
Copy link

mrPjer commented Feb 13, 2017

Your constructors in FinishingIntentsRule are wrongly named - they reference FinishingActivityTestRule. I also think you should call afterActivityFinished in FinishingActivityTestRule::finishActivity.

Can you do a write up on why this is necessary? It seems really hard to Google this problem and get relevant results.

@bootstraponline
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment