Unit test for Toast message content using Robolectric
Asked Answered
K

1

6

I have an Activity that does nothing but showing a Toast message like the following.

public MyActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        Toast.makeText(this, "Some message", Toast.LENGTH_LONG).show();
        finish(); // Finish the activity here. 
    }
}

I want to write a unit test using Robolectric to verify the Toast content. I tried the following.

@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {

    @Before
    public void setup() {
        Robolectric.buildActivity(MyActivity.class).create();
    }

    @Test
    public void testToast() {
        assertTrue(ShadowToast.showedCustomToast("Some message", R.string.some_message));
        assertThat(ShadowToast.getTextOfLatestToast().toString(), equalTo("Some message"));
    }
}

Looks like none of them are working. Any ideas?

Thanks in advance!

Kennel answered 21/12, 2019 at 5:36 Comment(10)
This seems problematic on several levels: 1) you can send a Toast from anywhere: why create an Activity to do it? 2) Why test the Android Toast system? If it didn't work, what would you do about it? 3) Toast is a thin wrapper around Binder: it transfers the text to an entirely different application and that application prints the message. It is going to be quite difficult to Shadow that.Jodijodie
did you try ShadowToast.showedToast(CharSequence message)?Nisus
@Nisus yes, I think I tried that one too. Thank you for your comment!Kennel
@G.BlakeMeike thank you so much for the comment. I am trying to respond here. The activity is just a dummy to provide an example. I do not want to test the Android Toast system. I want to test the content of the Toast and check if that appeared. Looks like Robolectric provides some Shadow technique, but so far none of them worked for me.Kennel
It is going to be very hard to test the output of the Toast. That happens in a completely different process. I suggest you test the input to the routine that composes the Toast, and assume that Toast.show works.Jodijodie
@G.BlakeMeike yes, that is correct. I could come up with a custom toast implementation and looks like it is working. Please check my answer below. I have also added a project in github with a working example. Please have a look if you have some time - github.com/masudias/toasterKennel
Using a Snackbar instead of a Toast would likely make it easier ...Karyn
@MartinZeitler, correct. However, I have Toast in my case. I found a workaround and added that as an answer. You might consider having a look. Thank you!Kennel
@ReazMurshed you might call it Toast, but it isn't really Toast anymore (as Blake explained). Only could imagine that one could get a handle to an actual Toast with UiAutomator2 (which can access views outside of the scope of the application).Karyn
@MartinZeitler Thank you for your insight! I will take a look at that.Kennel
K
1

I could manage to get a workaround for my problem and ended up having a custom layout for showing toast messages and testing the content of the toast using Robolectric as follows. I have added a GitHub project here with a working example.

public class MainActivityTest {

    @Before
    public void setup() {
        Robolectric.buildActivity(MainActivity.class).create();
    }

    @Test
    public void testToastMessage() {
        ToastLayoutBinding binding = DataBindingUtil.getBinding(ShadowToast.getLatestToast().getView());
        binding.executePendingBindings();

        assertEquals(binding.toastMsg.getContext().getString(R.string.some_toast),
                binding.toastMsg.getText());
    }

    @Test
    public void testToastDuration() {
        assertEquals(Toast.LENGTH_LONG, ShadowToast.getLatestToast().getDuration());
    }
}

The layout for the custom toast is simply as follows.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <import type="android.text.TextUtils" />

        <variable
            name="msg"
            type="String" />
    </data>

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:elevation="4dp">

        <TextView
            android:id="@+id/toast_msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:fontFamily="sans-serif"
            android:gravity="center"
            android:text="@{TextUtils.isEmpty(msg) ? @string/no_msg : msg}" />
    </FrameLayout>
</layout>

And the function for displaying the toast message is as follows.

/**
* Display a custom toast with some message provided as a function parameter
*
* @param activity An {@link Activity} where the toast message will be shown
* @param msg      a {@link String} that holds the message to be shown as a Toast
* @throws IllegalArgumentException if a null activity is passed to the function
* @apiNote If a null String is passed as a #msg parameter, then this function shows a default text (no_toast)
*/
public static void showToast(Activity activity, String msg) {

    if (activity == null) {
        throw new IllegalArgumentException("The passed in activity cannot be null");
    }

    if (msg == null) {
        msg = activity.getString(R.string.no_msg);
    }

    ToastLayoutBinding toastLayout = DataBindingUtil.inflate(
            activity.getLayoutInflater(), R.layout.toast_layout, null, false);
    toastLayout.setMsg(msg); // Set the toast message here

    Toast toast = new Toast(activity);
    toast.setView(toastLayout.getRoot());
    toast.setGravity(Gravity.TOP, 0, 200);
    toast.setDuration(Toast.LENGTH_LONG);
    toast.show();
}

I have used data-binding, however, this should also work with other layout inflation approaches.

I hope that helps other developers having the same problem.

Kennel answered 25/12, 2019 at 5:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.