Android Espresso Test Fails When Calling `ActivityScenario.onActivity` - android

When I try to run the following test, my Activity launches, but the View is never shown (it's just a black screen), then it times out and fails. If I remove the call to activityRule.doOnFirstFragment it passes fine.
I can see that the Activity started in logcat.
Anyone know why that call would make the test timeout?
#RunWith(AndroidJUnit4::class)
#LargeTest
class MyActivityTest {
#get:Rule
internal var activityRule = activityScenarioRule<MyActivity>()
private inline fun ActivityScenarioRule<*>.doOnFirstFragment(
crossinline block: () -> Unit
) {
scenario.onActivity {
// replace this with code to determine that first fragment was loaded
Thread.sleep(2000)
block()
}
}
#Test
fun canEnterPhoneNumber() {
activityRule.doOnFirstFragment {
onView(withText("Get Started"))
.perform(click())
onView(withText("Request PIN"))
.check(matches(not(isEnabled())))
onView(withHint("Phone Number"))
.perform(typeText("5555555555"), closeSoftKeyboard())
onView(withText("Request PIN"))
.check(matches(isEnabled()))
}
}
}

Related

Testing simple Loading+fetch flow with Coroutines as Android Instrumented tests with StandardTestDispatcher

I want to test the following very common usecase as an Instrumented Test in Android:
When clicking a button a fetch() function is called in my ViewModel
This function tells the view to show a loading-overlay
It executes a fetch in a coroutine
After the result is fetched it lets the view know to display the result
Here is the function in my Viewmodel:
fun fetch() {
_loading.value = true //loading is shown
viewModelScope.launch {
val results = fetchUseCase() //suspend function
_result.postValue(results)
_loading.postValue(false) //loading is not displayed
}
}
Here is the test which works according to this CodeLab https://developer.android.com/codelabs/advanced-android-kotlin-training-testing-survey#4:
#HiltAndroidTest
#UninstallModules(CoroutinesDispatcherModule::class)
#RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTestJunit4Deprecated {
#get:Rule
var hiltRule = HiltAndroidRule(this)
#ExperimentalCoroutinesApi
#get:Rule
var mainCoroutineRule = MainCoroutineRule()
#Before
fun setup() {
ActivityScenario.launch(HomeScreenActivity::class.java)
}
#ExperimentalCoroutinesApi
#Test
fun fetchTest() {
//pausing the long running tasks
mainCoroutineRule.pauseDispatcher()
//When clicking the button
onView(withId(R.id.load_measurement_button)).perform(click())
//loading is shown
onView(withId(R.id.loading_overlay))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
//continue fetch
mainCoroutineRule.resumeDispatcher()
// loading is not shown anymore and the result is there
onView(withId(R.id.loading_overlay))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
onView(withId(R.id.message))
.check(matches(withText("0")))
}
}
Unfortunately "pauseDispatcher()" and "resumeDispatcher" are Deprecated. I tried to use the "StandardTestDispatcher" and "advanceUntilIdle()" but it does not work as expected. The coroutine is never resumed. How can this test be rewritten, such that it works:
Without deprecated function calls
Without changing the productive code
?

How to assert next started activity for a composable test?

I have a composable with a button that launches one of the native activities (Google Settings).
To test this before compose (using Robolectric) I would do something like this:
My test:
#Test
fun `MyFragment - when button clicked - starts activity`() {
// ...
val shadowActivity: ShadowActivity = Shadow.extract(activity)
val nextStartedActivity = shadowActivity.nextStartedActivity
assertNotNull(nextStartedActivity)
assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, nextStartedActivity.action)
}
With compose tests (not using activity scenario) it's different. There is no activity handle, only a composeTestRule:
My test:
// ...
private val buttonNode get() = composeTestRule.onNodeWithContentDescription("Button")
#Test
fun `MyComposableToTest - when button clicked - starts activity`() {
composeTestRule.setContent {
MyComposableToTest()
}
buttonNode.assertExists().assertHasClickAction().assertIsEnabled().performClick()
// No possibility to get current activity
}
How can I assert that a new activity is started when testing a Composable?
Some context:
Android Gradle Plugin 7.0.3
Robolectric 4.7.3
Compose 1.1.0-beta04
You are able to fetch the context from the ComposeContentTestRule like this:
lateinit var context : Context
composeTestRule.setContent {
context = LocalContext.current
MyComposableToTest()
}
and then to assert the next started activity
val shadowActivity: ShadowActivity = Shadow.extract(context as ComponentActivity)
val nextStartedActivity = shadowActivity.nextStartedActivity
assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, nextStartedActivity.action)
This is how I did it for my instrumented test (NOT using Robolectric).
build.gradle[.kts]:
androidTestImplementation("androidx.test.espresso:espresso-intents:3.4.0")
The test class (in src/androidTest/... directory):
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
// ...
#RunWith(AndroidJUnit4::class)
class MainActivityInstrumentedTest {
#get:Rule val composeTestRule = createAndroidComposeRule<MainActivity>()
#Test fun testTheIntent() {
Intents.init() // IMPORTANT (to be able to use "intended" function)
composeTestRule.setContent {
MyAppTheme {
MainScreen()
}
}
composeTestRule.onNodeWithText("My Button").performClick()
intended(hasComponent(MySecondActivity::class.java.name))
Intents.release()
}
}

Waiting for livedata to complete in UI Espresso test

I'm new to testing and Espresso, so bear with me please.
I have an app with some simple image editing and I have decided to cover it with UI tests.
For starters I have decided to test the initial image uploading, processing and moving to the next screen.
here is the test I came up with so far:
#LargeTest
#RunWith(AndroidJUnit4::class)
class VerifyLoadImage {
lateinit var testContext: Context
#Rule
#JvmField
var mActivityTestRule = ActivityScenarioRule(MainActivity::class.java)
#Before
fun loadContext() {
testContext = InstrumentationRegistry.getInstrumentation().context
}
#Test
fun loadImageToCrop() {
mActivityTestRule.scenario.onActivity { mainActivity ->
// get the activity
val navigationFragment = mainActivity.supportFragmentManager.findFragmentById(R.id.fragmentContainer)
//verify that current fragment displayed is ImagePickerFragment
val currentFragment = navigationFragment?.getDisplayedChildFragment()?.let { it as? ImagePickerFragment }
?: throw AssertionError("currentFragment is not instance of ImagePickerFragment")
//call the method to upload the image from input stream, process it and then navigate to the crop screen
currentFragment.loadBitmapAndOpenCropScreen(AssetInputStreamProvider(testContext, "sample_image.jpg"))
//verify that crop screen is currently displayed
assert(navigationFragment.getDisplayedChildFragment() is CropFragment)
}
}
}
private fun Fragment.getDisplayedChildFragment() = childFragmentManager.fragments.first()
this is the code in currentFragment.loadBitmapAndOpenCropScreen
internal fun loadBitmapAndOpenCropScreen(inputStreamProvider: InputStreamProvider) {
activityViewModel.loadBitmap(inputStreamProvider).observe(viewLifecycleOwner) {
when (it) {
Loading -> showLoading()
is Success -> {
hideLoading()
findNavController().navigate(ImagePickerFragmentDirections.toCrop())
}
is Error -> hideLoading()
}
}
}
the problem is that when testing, the LiveData never changes updates at all [works normally when launching the app].
I would appreciate any help here.
Try InstantTaskExecutorRule
#Rule
#JvmField
var mInstantTaskExecutorRule = InstantTaskExecutorRule()

Android - Testing Fragments With Espresso by Using launchFragmentInContainer Never Completes

My test is never running to completion and I have absolutely no idea why. I can see the toast displayed on my phone's screen. There is absolutely nothing in the logs.
#RunWith(AndroidJUnit4::class)
#SmallTest
class BaseDataFragmentUITest
{
#Test
fun isDisplayingToastWhenFAILED_TO_UPDATE()
{
val fragmentScenario = launchFragmentInContainer<TestBaseDataFragmentImp>()
val toastString: String = context.resources.getString(com.developerkurt.gamedatabase.R.string.data_update_fail)
fragmentScenario.onFragment {
it.handleDataStateChange(BaseRepository.DataState.FAILED_TO_UPDATE)
onView(withText(toastString)).inRoot(withDecorView(not(it.requireActivity().getWindow().getDecorView()))).check(matches(isDisplayed()))
}
}
}
Apparently, Espresso assertions shouldn't be made inside of the onFragment block. So when I wrote the test like this it worked:
#Test
fun isDisplayingToastWhenFAILED_TO_UPDATE()
{
val fragmentScenario = launchFragmentInContainer<TestBaseDataFragmentImp>()
val toastString: String = context.resources.getString(com.developerkurt.gamedatabase.R.string.data_update_fail)
var decorView: View? = null
fragmentScenario.onFragment {
it.handleDataStateChange(BaseRepository.DataState.FAILED_TO_UPDATE)
decorView = it.requireActivity().getWindow().getDecorView()
}
onView(withText(toastString)).inRoot(withDecorView(not(decorView!!))).check(matches(isDisplayed()))
}

Method is called multiple times in unit tests but not in code

I am writing unit tests to a class that uses RxJava 2. When onNext() is called on the observer I expect onMenuLoaded() to be called once. In code it is called successfully once. But when I test this piece in unit tests the method is invoked 3 times.
The questions are how to make it be called only once in tests and why it is called more times in tests than in the actual code.
//in ViewModel class, under testing
fun loadMenu() {
menuInteractorImpl.getMainMenu()?.subscribeOn(Schedulers.io())?.observeOn(AndroidSchedulers.mainThread())?.subscribe(
{ items ->
onMenuLoaded(items)
},
{ error ->
//error handling logic
}
)?.let { compositeDisposables.add(it) }
}
//Test
#RunWith(PowerMockRunner::class)
#PowerMockRunnerDelegate(MockitoJUnitRunner::class)
#PrepareForTest(MenuInteractorImpl::class, MainMenuViewModel::class)
class MainMenuViewModelTest {
#get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
companion object {
#ClassRule
#JvmField
val schedulers = RxImmediateSchedulerRule()
}
#Before
fun setUp() {
doNothing().`when`(viewModel).startTimerToScrollViewPager()
}
#Test
fun `test load menu calls onMenuLoaded when success`() {
val mockObservable = Observable.just(mockDataFactory.mockMenu).doOnNext {
viewModel.onMenuLoaded(it)
}.subscribeOn(Schedulers.newThread())
Mockito.`when`(menuInteractorImpl.getMainMenu()).thenReturn(mockObservable)
viewModel.loadMenu() //this method is called 3 times
Mockito.verify(viewModel, times(1)).onMenuLoaded(any())
}
From the logs it is shown where the method is called
viewModel.loadMenu();
-> at com.example.mainmenu.MainMenuViewModelTest.test load menu calls onMenuLoaded when success(MainMenuViewModelTest.kt:88)
viewModel.loadMenu();
-> at org.powermock.core.MockGateway.doMethodCall(MockGateway.java:182)
viewModel.loadMenu();
-> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Your real code is calling onMenuLoaded(items) in onNext and then your mock observable is additionally calling it from doOnNext.
You need to remove the doOnNext part of your mock observable:
#Test
fun `test load menu calls onMenuLoaded when success`() {
val mockObservable = Observable.just(mockDataFactory.mockMenu)
.subscribeOn(Schedulers.newThread())
Mockito.`when`(menuInteractorImpl.getMainMenu()).thenReturn(mockObservable)
viewModel.loadMenu() //this method is called 3 times
Mockito.verify(viewModel, times(1)).onMenuLoaded(any())
}
The problem was in accidentally combining PowerMockito spy and Mockito mock. When importing Mockito's spy the issue was resolved.
EDIT:
I faced the same issue again and the solution was a different one. It looks like the same cause is combining PowerMockito and Mockito. I solved it this time by adding a doNothing block
doNothing().`when`(viewModel).myCall(true)

Categories

Resources