Testing Snackbars with Robolectric
It’s pretty easy to test Snackbars with Robolectric.
As an example, let’s say you have an app that allows you to write text documents. After you give that document a name and click “Save”, it saves the notes online, and then shows a Snackbar when that’s been successful:
When the document finishes saving, our ViewModel’s isSaved
field is set to true
, and the observer on that field will show that Snackbar.
viewModel.isSaved.observe(
viewLifecycleOwner,
{ isSaved ->
if (isSaved) {
Snackbar.make(
viewBinding.root,
resources.getString(
R.string.success_message,
viewModel.documentName.value
),
Snackbar.LENGTH_LONG
).show()
viewModel.isSaved.value = false
}
}
)
In our Robolectric test, we can trigger this code with the following:
myFragment.viewModel.documentName.value = "My Notes"
myFragment.viewModel.isSaved.postValue(true)
We can then use the following Espresso syntax to locate the Snackbar and verify it shows the correct text:
onView(
allOf(
isAssignableFrom(Snackbar.SnackbarLayout::class.java),
hasDescendant(
withText(
myFragment.resources.getString(
R.string.success_message,
myFragment.viewModel.documentName.value
)
)
)
)
)
.check(matches(isDisplayed()))
This of course isn’t the only way to locate and verify the contents of a Snackbar, but it is one approach that works.
It’s worth noting that by using Espresso as in the above syntax, it automatically waits until the main thread is idle, which is necessary to ensure the Snackbar actually got rendered.
But now how would we test that the Snackbar is gone after the specified duration of Snackbar.LENGTH_LONG
? We can advance the main Looper by a certain number of seconds and check that the Snackbar is gone with:
Shadows.shadowOf(
Looper.getMainLooper()
).idleFor(<num_seconds>, TimeUnit.SECONDS)
onView(
allOf(
isAssignableFrom(Snackbar.SnackbarLayout::class.java)
)
)
.check(doesNotExist())
We just need to determine how long to wait. To do that, you need to look at the actual time value to which Snackbar.LENGTH_LONG
corresponds. To do that, you have to look at the source code of SnackbarManager
, which is a package-private class so you can’t access it directly from your source nor test code, but you can look at it yourself. At the time of this writing:
private static final int SHORT_DURATION_MS = 1500;
private static final int LONG_DURATION_MS = 2750;
So we can wait 3 seconds and verify that the Snackbar is gone with:
Shadows.shadowOf(
Looper.getMainLooper()
).idleFor(3, TimeUnit.SECONDS)
onView(
allOf(
isAssignableFrom(Snackbar.SnackbarLayout::class.java)
)
)
.check(doesNotExist())
It’s unfortunate because if the implementation of SnackbarManager
ever changes the actual duration of LENGTH_LONG, it could break our test. But it’s about the best we can do. Of course, if we’re specifying a specific duration in our source-under-test, then we can just use that (plus some padding).