Skip to content

Commit

Permalink
Merge pull request #450 from nimblehq/feature/299-Add-ui-test-home-se…
Browse files Browse the repository at this point in the history
…cond-screen

[#299] [Sample] Add UI tests for XML sample code
  • Loading branch information
ryan-conway authored May 5, 2023
2 parents 9d86348 + da63420 commit b9304c7
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package co.nimblehq.sample.xml.extension

import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
import androidx.navigation.NavArgs
import androidx.navigation.fragment.navArgs

@MainThread
inline fun <reified Args : NavArgs> Fragment.provideNavArgs(): Lazy<Args> =
OverridableLazy(navArgs())
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package co.nimblehq.sample.xml.ui.screens.second
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.fragment.navArgs
import co.nimblehq.sample.xml.R
import co.nimblehq.sample.xml.databinding.FragmentSecondBinding
import co.nimblehq.sample.xml.extension.provideNavArgs
import co.nimblehq.sample.xml.extension.provideViewModels
import co.nimblehq.sample.xml.ui.base.BaseFragment
import dagger.hilt.android.AndroidEntryPoint
Expand All @@ -14,7 +14,7 @@ import dagger.hilt.android.AndroidEntryPoint
class SecondFragment : BaseFragment<FragmentSecondBinding>() {

private val viewModel: SecondViewModel by provideViewModels()
private val args: SecondFragmentArgs by navArgs()
private val args: SecondFragmentArgs by provideNavArgs()

override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentSecondBinding
get() = { inflater, container, attachToParent ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package co.nimblehq.sample.xml.test

import androidx.navigation.NavArgs
import co.nimblehq.sample.xml.extension.OverridableLazy
import kotlin.reflect.KProperty1
import kotlin.reflect.jvm.isAccessible

fun <Arg : NavArgs, T> T.replace(
argumentDelegate: KProperty1<T, Arg>,
argument: Arg
) {
argumentDelegate.isAccessible = true
(argumentDelegate.getDelegate(this) as OverridableLazy<Arg>).implementation = lazy { argument }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package co.nimblehq.sample.xml.ui.screens.home

import androidx.core.view.isVisible
import co.nimblehq.sample.xml.databinding.FragmentHomeBinding
import co.nimblehq.sample.xml.model.UiModel
import co.nimblehq.sample.xml.test.TestNavigatorModule.mockMainNavigator
import co.nimblehq.sample.xml.test.getPrivateProperty
import co.nimblehq.sample.xml.test.replace
import co.nimblehq.sample.xml.ui.BaseFragmentTest
import co.nimblehq.sample.xml.ui.base.NavigationEvent
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.kotest.matchers.booleans.shouldBeFalse
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.mockk.*
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.*
import org.robolectric.shadows.ShadowToast
import java.util.*

@HiltAndroidTest
class HomeFragmentTest : BaseFragmentTest<HomeFragment, FragmentHomeBinding>() {

private val mockViewModel = mockk<HomeViewModel>(relaxed = true)

@get:Rule
var hiltRule = HiltAndroidRule(this)

@Before
fun setUp() {
hiltRule.inject()
}

@Test
fun `When launching fragment, it displays the recycler view`() {
launchFragment()
fragment.binding.rvHome.isVisible.shouldBeTrue()
}

@Test
fun `When launching fragment and view model emits loading, it displays the progress bar`() {
every { mockViewModel.isLoading } returns MutableStateFlow(true)

launchFragment()
fragment.binding.pbHome.isVisible.shouldBeTrue()
}

@Test
fun `When launching fragment and view model does not emit loading, it does not display the progress bar`() {
every { mockViewModel.isLoading } returns MutableStateFlow(false)

launchFragment()
fragment.binding.pbHome.isVisible.shouldBeFalse()
}

@Test
fun `When launching fragment and view model emits list of item, it displays the recycler view with items`() {
val items = arrayListOf(
UiModel(UUID.randomUUID().toString()),
UiModel(UUID.randomUUID().toString()),
UiModel(UUID.randomUUID().toString())
)
every { mockViewModel.uiModels } returns MutableStateFlow(items)

launchFragment()
fragment.binding.rvHome.adapter?.itemCount shouldBe items.size
}

@Test
fun `When launching fragment and view model emits first time launch, it displays a toast message`() {
every { mockViewModel.isFirstTimeLaunch } returns MutableStateFlow(true)

launchFragment()
ShadowToast.getTextOfLatestToast() shouldBe "This is the first time launch"
}

@Test
fun `When launching fragment and view model does not emit first time launch, it does not display a toast message`() {
every { mockViewModel.isFirstTimeLaunch } returns MutableStateFlow(false)

launchFragment()
ShadowToast.getTextOfLatestToast() shouldBe null
}

@Test
fun `When view model emits navigation event to second fragment, it should navigate to second screen`() {
val uiModel = UiModel(UUID.randomUUID().toString())
every { mockViewModel.navigator } returns MutableStateFlow(NavigationEvent.Second(uiModel))
every { mockMainNavigator.navigate(any()) } returns Unit

launchFragment()
verify { mockMainNavigator.navigate(NavigationEvent.Second(uiModel)) }
}

private fun launchFragment() {
launchFragmentInHiltContainer<HomeFragment>(
onInstantiate = {
replace(getPrivateProperty("viewModel"), mockViewModel)
navigator = mockMainNavigator
}
) {
fragment = this
fragment.navigator.shouldNotBeNull()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package co.nimblehq.sample.xml.ui.screens.second

import androidx.core.view.isVisible
import co.nimblehq.sample.xml.R
import co.nimblehq.sample.xml.databinding.FragmentSecondBinding
import co.nimblehq.sample.xml.model.UiModel
import co.nimblehq.sample.xml.test.getPrivateProperty
import co.nimblehq.sample.xml.test.replace
import co.nimblehq.sample.xml.ui.BaseFragmentTest
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.*
import java.util.*

@HiltAndroidTest
class SecondFragmentTest : BaseFragmentTest<SecondFragment, FragmentSecondBinding>() {

private val mockViewModel = mockk<SecondViewModel>(relaxed = true)
private val mockArgs = mockk<SecondFragmentArgs>(relaxed = true)

private val uiModel = UiModel(UUID.randomUUID().toString())

@get:Rule
var hiltRule = HiltAndroidRule(this)

@Before
fun setUp() {
hiltRule.inject()

every { mockArgs.uiModel } returns uiModel
}

@Test
fun `When launching fragment, it displays the text view`() {
launchFragment()
fragment.binding.tvSecondId.isVisible.shouldBeTrue()
}

@Test
fun `When launching fragment and view model with id, it displays the text view with id content`() {
every { mockViewModel.id } returns MutableStateFlow(uiModel.id)

launchFragment()
fragment.binding.tvSecondId.text shouldBe fragment.getString(
R.string.second_id_title,
uiModel.id
)
}

private fun launchFragment() {
launchFragmentInHiltContainer<SecondFragment>(
onInstantiate = {
replace(getPrivateProperty("viewModel"), mockViewModel)
replace(getPrivateProperty("args"), mockArgs)
}
) {
fragment = this
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package co.nimblehq.template.xml.extension

import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
import androidx.navigation.NavArgs
import androidx.navigation.fragment.navArgs

@MainThread
inline fun <reified Args : NavArgs> Fragment.provideNavArgs(): Lazy<Args> =
OverridableLazy(navArgs())
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package co.nimblehq.template.xml.test

import androidx.navigation.NavArgs
import co.nimblehq.template.xml.extension.OverridableLazy
import kotlin.reflect.KProperty1
import kotlin.reflect.jvm.isAccessible

fun <Arg : NavArgs, T> T.replace(
argumentDelegate: KProperty1<T, Arg>,
argument: Arg
) {
argumentDelegate.isAccessible = true
(argumentDelegate.getDelegate(this) as OverridableLazy<Arg>).implementation = lazy { argument }
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class HomeFragmentTest : BaseFragmentTest<HomeFragment, FragmentHomeBinding>() {
}

@Test
fun `When initializing fragment, it displays the title correctly`() {
fun `When launching fragment, it displays the title correctly`() {
launchFragment()
fragment.binding.tvTitle.text.toString() shouldBe fragment.resources.getString(R.string.app_name)
}
Expand Down

0 comments on commit b9304c7

Please sign in to comment.