Search code examples
androidprogressdialogandroid-dialogfragmentandroid-progressbarandroid-espresso

Check if a custom progress DialogFragment is shown or not using Espresso?


I have a custom progress dialog with a progressbar and a message displayed during network calls.(For e.g. Logging in..., Fetching data etc., ).

I want to write a test to verify the dialogfragment with given text is displayed or not.

CustomProgressDialog.java

public class CustomProgressDialog extends DialogFragment {

  private static final String KEY_MESSAGE = "message";
  public static final String TAG_PROGRESS_DIALOG = "progress_dialog";

  @BindView(R.id.progressbar) ProgressBar mProgressBar;
  @BindView(R.id.progress_textview) TextView mProgressTextView;

  private Unbinder mUnbinder;

  public static CustomProgressDialog start(String progressMessage) {
    CustomProgressDialog dialog = new CustomProgressDialog();

    Bundle bundle = new Bundle();
    bundle.putString(KEY_MESSAGE, progressMessage);
    dialog.setArguments(bundle);
    return dialog;
  }

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRetainInstance(true);
    setCancelable(false);
  }

  @Override public Dialog onCreateDialog(Bundle savedInstanceState) {
    LayoutInflater inflater = getActivity().getLayoutInflater();

    Bundle bundle = getArguments();
    String mProgressMessage;
    if (bundle != null && bundle.containsKey(KEY_MESSAGE)) {
      mProgressMessage = bundle.getString(KEY_MESSAGE);
    } else {
      mProgressMessage = getActivity().getString(R.string.progress_loading);
    }

    View view = inflater.inflate(R.layout.dialog_custom_progress, null);
    mUnbinder = ButterKnife.bind(this, view);
    mProgressTextView.setText(mProgressMessage);

    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    builder.setView(view);

    return builder.create();
  }


  /**
   * Helper method to show a progress dialog with a message.
   */
  public static void showDialog(FragmentActivity activity, String message) {
    showDialog(activity, message, TAG_PROGRESS_DIALOG);
  }

  /**
   * Helper method to show a progress dialog with a message.
   */
  public static void showDialog(FragmentActivity activity, String message, String tag) {
    CustomProgressDialog progressDialog = CustomProgressDialog.start(message);
    progressDialog.show(activity.getSupportFragmentManager(), tag);
  }

  public static void hideDialog(FragmentActivity activity) {
    hideDialog(activity, TAG_PROGRESS_DIALOG);
  }

  /**
   * Helper method to hide a progress dialog.
   */
  public static void hideDialog(FragmentActivity activity, String tag) {
    FragmentManager fragmentManager = activity.getSupportFragmentManager();
    DialogFragment dialogFragment = (DialogFragment) fragmentManager.findFragmentByTag(tag);
    if (dialogFragment != null) {
      Timber.d("Gonna dismiss the dialog.");
      dialogFragment.dismiss();
    }
  }

  @Override public void onDestroyView() {
    if (getDialog() != null && getRetainInstance()) {
      getDialog().setDismissMessage(null);
      mUnbinder.unbind();
    }
    super.onDestroyView();
  }
}

dialog_custom_progress.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:layout_marginTop="@dimen/spacing_small"
    android:layout_marginBottom="@dimen/spacing_small"
    android:padding="@dimen/spacing_xlarge"
    >

  <ProgressBar
      android:id="@+id/progressbar"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentLeft="true"
      android:layout_centerVertical="true"
      android:indeterminate="true"
      android:indeterminateTint="@color/primary_dark"
      android:indeterminateTintMode="src_in"
      />

  <TextView
      android:id="@+id/progress_textview"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_centerVertical="true"
      android:gravity="left"
      android:layout_marginLeft="@dimen/spacing_medium"
      android:layout_toRightOf="@+id/progressbar"
      android:maxLines="3"
      tools:text="@string/placeholder_progress_text"
      style="@style/ProgressTextStyle"
      />

</RelativeLayout>

LoginFragment.java

private void doLogin() {
  //blah.. blah ..
  CustomProgressDialog.showDialog(getActivity(),
        getActivity().getString(R.string.progress_logging_in));
}

Sample testcase

 @Test public void checkProgressBar_displayedWhileLoggingIn() throws Exception {
    // GIVEN
    ...... 

    // WHEN
    onView(LOGIN_BUTTON).perform(click());

    // THEN
    // TODO check for progress bar is displayed.
    //onView(withText(R.string.progress_logging_in)).check(matches(isDisplayed()));                
    onView(withId(R.id.progress_textview)).check(matches(isDisplayed()));

  }

I'd got the following error for the above testcase:

android.support.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with id: com.custom.android.internal.debug:id/progress_textview

Although progress_textview is displayed on the screen.


Solution

  • Espresso doesn't work well when indeterminate progressbar is enabled.

    Refer:
    1. Testing progress bar on Android with Espresso.
    2. ProgressBars and Espresso

    Based on the solutions given in the posts and by following the gist, I created two different versions of progressbar for each productFlavor like below:

    productFlavor(Debug)/ProgressBar.java

    import android.annotation.TargetApi;
    import android.content.Context;
    import android.os.Build;
    import android.util.AttributeSet;
    import android.view.animation.Animation;
    
    /**
     * Progressbar that is used in UI Tests
     * Prevents the progressbar from ever showing and animating
     * Thus allowing Espresso to continue with tests and Espresso won't be blocked
     */    
    public class ProgressBar extends android.widget.ProgressBar {
    
      public ProgressBar(Context context) {
        super(context);
        setUpView();
      }
    
      public ProgressBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        setUpView();
      }
    
      public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setUpView();
      }
    
      @TargetApi(Build.VERSION_CODES.LOLLIPOP)
      public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        setUpView();
      }
    
      private void setUpView() {
        this.setVisibility(GONE);
      }
    
      @Override
      public void setVisibility(int v) {
        // Progressbar should never show
        v = GONE;
        super.setVisibility(v);
      }
    
      @Override
      public void startAnimation(Animation animation) {
        // Do nothing in test cases, to not block ui thread
      }
    }
    

    productFlavor(Production)/ProgressBar.java

    import android.annotation.TargetApi;
    import android.content.Context;
    import android.os.Build;
    import android.util.AttributeSet;
    
    /**
     * Progressbar that just calls the default implementation of Progressbar
     * Should always be used instead of {@link android.widget.ProgressBar}
     */
    public class ProgressBar extends android.widget.ProgressBar {
    
        public ProgressBar(Context context) {
            super(context);
        }
    
        public ProgressBar(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
    }
    

    And modified the layout dialog_custom_progress.xml progressbar with:

    <com.projectname.android.custom.ProgressBar
          android:id="@+id/progressbar"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_alignParentLeft="true"
          android:layout_centerVertical="true"
          android:indeterminate="true"
          android:indeterminateTint="@color/primary_dark"
          android:indeterminateTintMode="src_in"
          />
    

    Finally test case runs successfully.