27

I have onView(withId(R.id.check_box)).perform(click()), but i only want to do this if the check box is not already checked. How can I do this in espresso?

kyrax
  • 1,192
  • 3
  • 13
  • 29

6 Answers6

56

I also wanted to toggle a checkbox/switch depending on it's previous state. At first, I tried this to toggle ON a checkbox that was OFF:

onView(withId(R.id.checkbox)).check(matches(isNotChecked())).perform(scrollTo(), click());

...and this to toggle OFF a checkbox that was ON:

onView(withId(R.id.checkbox)).check(matches(isChecked())).perform(scrollTo(), click());

However, this doesn't work, since Espresso will be looking for a specific toggle state before it performs the action. Sometimes, you don't know whether it's ON or OFF beforehand.

My solution is to use a custom ViewAction to turn OFF/ON any checkable object (Switch, Checkbox, etc.) that isn't dependent on previous state. So, if it's already ON, it'll stay ON. If it's OFF, it'll toggle ON. Here's the ViewAction:

public static ViewAction setChecked(final boolean checked) {
    return new ViewAction() {
        @Override
        public BaseMatcher<View> getConstraints() {
            return new BaseMatcher<View>() {
                @Override
                public boolean matches(Object item) {
                    return isA(Checkable.class).matches(item);
                }

                @Override
                public void describeMismatch(Object item, Description mismatchDescription) {}

                @Override
                public void describeTo(Description description) {}
            };
        }

        @Override
        public String getDescription() {
            return null;
        }

        @Override
        public void perform(UiController uiController, View view) {
            Checkable checkableView = (Checkable) view;
            checkableView.setChecked(checked);
        }
    };
}

And here's how you use it (in this example, when you want to toggle to ON):

onView(withId(R.id.toggle)).perform(scrollTo(), setChecked(true));
0xMatthewGroves
  • 3,181
  • 3
  • 26
  • 43
  • Perfect, thank you @FrostRocket The only thing that I had to change was remove "scrollTo()" ViewAction, because I got error telling that scrollTo is not supported for my checkbox: `onView(withId(R.id.global_search)).perform(setChecked(globalSearch));` As you see, I wanted to set the checkbox to the value of provided "globalSearch" var – yvolk Jul 17 '17 at 05:01
  • I updated the answer adding check of current state of the checkbox and changing it only if its state is not as needed to avoid unnecessary events: `if (checkableView.isChecked() != checked) { checkableView.setChecked(checked); }` – yvolk Jul 17 '17 at 05:11
  • The internal implementation of setChecked already takes this into account. – 0xMatthewGroves Jul 17 '17 at 06:55
  • This is an excellent choice. However, know that if you are doing an action in your activity based on the checkbox being *clicked* then this will not trigger it. – Stonz2 Apr 25 '18 at 18:48
  • @Stonz2 has a good point -- `setChecked()` won't trigger listener actions. To fix that, click the view instead: `if (checkableView.isChecked() != checked) click().perform(uiController, view);` – Jerry101 Sep 07 '19 at 23:58
6

Just as @FrostRocket suggested but written in Kotlin.

We define a custom action that can only be performed on checkable items (as specified in constraints). So we safely cast the view to Checkable to access setCheckable method.

fun setChecked(checked: Boolean) = object : ViewAction {
    val checkableViewMatcher = object : BaseMatcher<View>() {
        override fun matches(item: Any?): Boolean = isA(Checkable::class.java).matches(item)
        override fun describeTo(description: Description?) {
            description?.appendText("is Checkable instance ")
        }
    }

    override fun getConstraints(): BaseMatcher<View> = checkableViewMatcher
    override fun getDescription(): String? = null
    override fun perform(uiController: UiController?, view: View) {
        val checkableView: Checkable = view as Checkable
        checkableView.isChecked = checked
    }
}
Achraf Amil
  • 1,275
  • 16
  • 20
2

This seems to be part of your test, that the checkbox needs to be checked.

You can check this by:

onView(withId(R.id.checkbox)).check(matches(not(isChecked())));

If this fails, your test will fail, which may be good in your case. Then, you can perform the click you want after this case matches.

If this is not the situation you want, can you explain more on what you are trying to do in this Espresso Test?

Sufian
  • 6,405
  • 16
  • 66
  • 120
Sahith Reddy
  • 263
  • 3
  • 10
  • I want to make sure the checkbox is checked before i do my test. The reason it may be unchecked is because another test left it unchecked. – kyrax Jun 14 '16 at 21:38
  • Espresso is not supposed to be able to set any state of views or manipulate views in any way. If there is no other way to structure your tests than the previous answer would work, but again goes against espresso conventions. I would recommend structuring your tests in a way that would make them more modular so they do not have to manipulate any views. @kyrax – Sahith Reddy Jun 15 '16 at 00:42
  • 1
    @kyrax Can you accept my answer as accepted because it should be known that using an activity in espresso tests is very very bad. – Sahith Reddy Jun 20 '16 at 21:48
  • 1
    @kryax I'm not sure why this is the accepted answer, as this doesn't solve the original question. Check out the solution I posted if you want to manipulate the view without failing your tests. – 0xMatthewGroves Sep 23 '16 at 00:31
1

This simple solution might help you

onView(withId(R.id.checkBox_tea)).check(matches(isNotChecked())).perform(click()).check(matches(isChecked()));

worked perfectly for me.

Md Nakibul Hassan
  • 2,716
  • 1
  • 15
  • 19
  • I don't see how this is not the accepted answer.... It's the only one where nothing is manipulated/overridden, and it needs only one line, no extra classes – Michiel van der Blonk Dec 29 '21 at 00:23
0

You have something like this in your code:

@Rule
public ActivityTestRule<ActivityMain> ActivityMainRule = new ActivityTestRule<>(ActivityMain.class);

Try

if (!ActivityMainRule.getActivity().findViewById(R.id.check_box).isChecked()) {
    onView(withId(R.id.check_box)).perform(click())    
}
user35603
  • 765
  • 2
  • 8
  • 24
  • 3
    That is not recommended according to Google it goes against how Espresso works and is very dangerous https://youtu.be/isihPOY2vS4?t=4m12s – Sahith Reddy Jun 14 '16 at 19:56
0

I had the same problem an wrote an own ViewAction that always unchecks my SwitchCompat

class UncheckViewAction implements ViewAction{

    @Override
    public Matcher<View> getConstraints() {
        return new Matcher<View>() {
            @Override
            public boolean matches(Object item) {
                return isA(SwitchCompat.class).matches(item);
            }

            @Override
            public void describeMismatch(Object item, Description mismatchDescription) {

            }

            @Override
            public void _dont_implement_Matcher___instead_extend_BaseMatcher_() {

            }

            @Override
            public void describeTo(Description description) {

            }
        };
    }

    @Override
    public String getDescription() {
        return null;
    }

    @Override
    public void perform(UiController uiController, View view) {
        SwitchCompat scView = (SwitchCompat) view;
        scView.setChecked(false);
    }
}

and used it like this:

onView(withId(R.id.check_box)).perform(new UncheckViewAction())

now i can be sure that the found switch is always unchecked, no matter if it was checked or unchecked before.

richard
  • 724
  • 2
  • 14
  • 36