Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package com.tsview.robolectric;
- import android.app.Activity;
- import android.app.Application;
- import android.content.Intent;
- import android.os.Build;
- import android.os.Bundle;
- import android.support.annotation.NonNull;
- import android.support.annotation.Nullable;
- import android.support.v4.app.Fragment;
- import android.support.v4.app.FragmentActivity;
- import android.widget.LinearLayout;
- import org.junit.runner.RunWith;
- import org.robolectric.Robolectric;
- import org.robolectric.RobolectricTestRunner;
- import org.robolectric.RuntimeEnvironment;
- import org.robolectric.annotation.Config;
- import org.robolectric.util.ActivityController;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.atomic.AtomicInteger;
- /** Test holder that simplify robolectric UI tests execution. */
- @Config(sdk = Build.VERSION_CODES.LOLLIPOP, manifest = "src/main/AndroidManifest.xml")
- @RunWith(RobolectricTestRunner.class)
- public abstract class RobolectricTestsHelper {
- /** New line delimiter. */
- public final static String NEW_LINE = "\r\n";
- /** Trace activity/fragment lifecycle states. Default: false. */
- public static final String OPTION_LIFECYCLE = "trace-lifecycle";
- /** Should log messages be with timestamp. By default: true; */
- public final static String OPTION_TIMESTAMP = "use-timestamp";
- /** Async Test synchronization instance. */
- public final AtomicInteger SYNC_COUNTER = new AtomicInteger();
- /** Dynamic options/configurations that influence on tests output. */
- public final Map<String, Boolean> Options = new HashMap<>();
- /** Standard Output Logger. Helps to save some useful results of tests as a part of execution. */
- private final StringBuilder mLog = new StringBuilder(64 * 1024).append(NEW_LINE);
- /**
- * Perform full lifecycle emulation for activity. When Activity is in visible state is possible to execute some
- * additional actions.
- *
- * @param onRecreate provide instance if you want to test recreation of the activity, otherwise NULL.
- */
- public <T extends Activity> ActivityController<T> fullLifecycle(
- @NonNull ActivityController<T> controller,
- @Nullable final Runnable onRestart,
- @Nullable final Runnable onResume,
- @Nullable final Runnable onVisible,
- @Nullable final RecreateRunnable<T> onRecreate) {
- Bundle savedInstanceState = null;
- final boolean doTrace = option(OPTION_LIFECYCLE, false);
- // do recreate only if defined callback
- int recreateLoops = (null != onRecreate) ? 1 : 0;
- do {
- if (doTrace) trace("state - onCreate : " + recreateLoops);
- controller.create(savedInstanceState);
- // CYCLE #1: emulate activity restart
- int lifeLoops = 1;
- do {
- if (doTrace) trace("state - onStart : " + lifeLoops);
- controller.start();
- if (null != savedInstanceState) {
- controller.restoreInstanceState(savedInstanceState);
- controller.postCreate(savedInstanceState);
- }
- // CYCLE #1.1: emulate show/hide
- int loops = 1;
- do {
- if (doTrace) trace("state - onResume : " + loops);
- if (null != onResume) onResume.run();
- controller.resume(); // --> onPostResume()
- // TODO: onAttachedToWindow()
- controller.visible(); // --> onCreateOptionsMenu(), onUserInteraction()
- if (null != onVisible) onVisible.run();
- controller.userLeaving();
- controller.pause();
- if (doTrace) trace("state - onPause");
- loops--;
- } while (loops >= 0);
- // CHECK-ME: robolectric call it before #pause()
- controller.saveInstanceState(savedInstanceState = new Bundle());
- controller.stop();
- if (doTrace) trace("state - onStop");
- // TODO: onRetainNonConfigurationInstance() --> controller.get().onRetainNonConfigurationInstance();
- // go-to onRestart() state
- if (lifeLoops > 0) {
- if (null != onRestart) onRestart.run();
- // during restart we do not need the savedInstanceState, drop the instance
- savedInstanceState = null;
- if (doTrace) trace("state - onRestart");
- controller.restart();
- }
- lifeLoops--;
- } while (lifeLoops >= 0);
- if (doTrace) trace("state - onDestroy");
- controller.destroy();
- // save instance and recover it for additional lifecycle loop
- if (recreateLoops > 0) {
- if (null != onRecreate)
- controller = onRecreate.recreate(controller);
- if (doTrace) trace("state - recreate : " + recreateLoops);
- controller.attach();
- }
- recreateLoops--;
- } while (recreateLoops >= 0);
- return controller; // can be a new instance due to re-create execution
- }
- //region --> Fragment Lifecycle emulation
- public static <T extends Fragment> ActivityController<FragmentHostingActivity> inject(
- @NonNull ActivityController<FragmentHostingActivity> controller,
- @Nullable Configure<T> configuration) {
- controller.get().setConfiguration(configuration);
- return controller;
- }
- public static <T extends Fragment> ActivityController<FragmentHostingActivity> testFragment(final Class<T> clazz) {
- return testFragment(clazz, null);
- }
- public static <T extends Fragment> ActivityController<FragmentHostingActivity> testFragment(
- @NonNull final Class<T> clazz, @Nullable final Bundle saved) {
- final Application application = RuntimeEnvironment.application;
- final Intent intent = new Intent(application, clazz);
- if (null != saved) intent.putExtras(saved);
- return testActivity(FragmentHostingActivity.class, intent);
- }
- //endregion
- //region Options
- /** Check the option. */
- public boolean option(final String name, final boolean $default) {
- if (Options.containsKey(name)) {
- return Options.get(name);
- }
- return $default;
- }
- /** Set option to a new value. */
- public void setOption(final String name, final boolean value) {
- Options.put(name, value);
- }
- //endregion
- //region Standard Output
- /** Get access to the logs memory storage directly. */
- @NonNull
- protected StringBuilder getRawLogger() {
- return mLog;
- }
- public void log(final Level level, final String tag, final String msg) {
- final long last = option(OPTION_THREAD_TIMESTAMP, true) ? mLastMsg.get() : mTimestamp;
- final String timestamp = String.format(Locale.US, " (+%-5.3f ms)", millisFrom(last));
- final boolean doTimestamp = option(OPTION_TIMESTAMP, true);
- mLog.append(level.toString().charAt(0)).append(" : ")
- .append(tag).append(" : ")
- .append(msg)
- .append(doTimestamp ? timestamp : "")
- .append(NEW_LINE);
- // update timestamps
- mLastMsg.set(mTimestamp = System.nanoTime());
- }
- /** trace single message without formatting. */
- public void trace(final String msg) {
- log(Level.INFO, "--", msg);
- }
- /** trace with formatting. */
- public void trace(final String format, final Object... args) {
- final String message = (null == args) ? format : String.format(Locale.US, format, args);
- log(Level.INFO, "--", message);
- }
- /**
- * Convert nanoseconds to milliseconds with high accuracy.
- *
- * @param nanos nanoseconds to convert.
- * @return total milliseconds.
- */
- public static double toMillis(final long nanos) {
- return nanos / 1000.0 /* micros in 1 milli */ / 1000.0 /* nanos in 1 micro */;
- }
- /** Get duration in milliseconds from start point till now. */
- public static double millisFrom(final long start) {
- return toMillis(System.nanoTime() - start);
- }
- //endregion
- //region --> Nested Declarations
- /** Implement interface for making possible testing of the activity re-creation. */
- public interface RecreateRunnable<T extends Activity> {
- ActivityController<T> recreate(final ActivityController<T> old);
- }
- /** Configure instance of the fragment before it injecting into activity. */
- public interface Configure<T extends Fragment> {
- void configure(final T instance);
- }
- /** Hosting activity that can be used for any fragment testing. */
- public static class FragmentHostingActivity extends FragmentActivity {
- /** Tag for easier finding of the fragment. */
- public static final String TAG_TEST_FRAGMENT = "TAG_TEST_FRAGMENT";
- /** Cache of the instance. */
- private Fragment mInstance;
- private Configure<Fragment> mConfiguration;
- @Override
- protected void onCreate(@Nullable final Bundle saved) {
- super.onCreate(saved);
- // first time call, otherwise restore from saved instance state should work
- if (null == saved) {
- // root view creation
- final LinearLayout view = new LinearLayout(this);
- view.setId(R.id.tag_view_holder);
- setContentView(view);
- // fragment instance creation
- final Fragment fragment = getFragmentInstantiate(getIntent());
- if (null != mConfiguration) {
- mConfiguration.configure(fragment);
- }
- // integrate fragment
- getSupportFragmentManager()
- .beginTransaction()
- .replace(R.id.tag_view_holder, fragment, TAG_TEST_FRAGMENT)
- .commit();
- }
- }
- /** Get instance of the Fragment from the fragment manager. Life instance. */
- @Nullable
- public Fragment getTestFragment() {
- return getSupportFragmentManager().findFragmentByTag(TAG_TEST_FRAGMENT);
- }
- /** Create a new instance of the Fragment based on provided intent. */
- public Fragment getFragmentInstantiate(@NonNull final Intent intent) {
- if (null != mInstance)
- return mInstance;
- final String className = intent.getComponent().getClassName();
- mInstance = Fragment.instantiate(this, className, intent.getExtras());
- return mInstance;
- }
- public void setFragmentInstantiate(final Fragment fragment) {
- mInstance = fragment;
- }
- public <T extends Fragment> FragmentHostingActivity setConfiguration(final Configure<T> configuration) {
- mConfiguration = (Configure<Fragment>) ((Configure<?>) configuration);
- return this;
- }
- }
- //endregion
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement