簡體   English   中英

必須使用父活動引用時,如何使用“FragmentScenario”單獨測試 Fragment

[英]How to test Fragment in isolation using `FragmentScenario` when you have to use parent activity reference

我打算使用FragmentScenarioFragmentScenario測試片段。 在這個片段中,我正在訪問父活動以調用一些方法。 為此,我使用了CommonActivityOperations接口。 我在這個應用程序中使用導航架構組件。

interface CommonActivityOperations {
    fun closeSearchBar()
    fun navigateBackWithResult(resultFor: String, result: Bundle)
}

當我嘗試在我的AttractionDestinationFragment上運行測試時,它給了我以下錯誤。

java.lang.ClassCastException: androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity cannot be cast to .interfaces.CommonActivityOperations

AttractionDestinationFragment

const val DESTINATION_DATA_EXTRA = "destination_data_result"

class AttractionDestinationFragment : BaseFragment(), ItemClickListener, ToolbarSearchChangeListener{

    private lateinit var navigationController: NavController
    private lateinit var defaultAttractionList: MutableList<AttractionDestinations>
    private lateinit var attractionDestinationRecyclerViewAdapter: AttractionDestinationRecyclerViewAdapter

    private val viewModel by lazy { getViewModel<AttractionDestinationViewModel>() }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_attraction_destination, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

        initialization(view)
        observeAttractionDestinations()
    }

    private fun initialization(view: View) {

        navigationController = Navigation.findNavController(view)

        defaultAttractionList = mutableListOf(...)

        attractionDestinationRecyclerViewAdapter =
                AttractionDestinationRecyclerViewAdapter(defaultAttractionList)
        attractionDestinationRecyclerViewAdapter.itemClickListener = this
        attractionDestinationRecyclerView.addItemDecoration(SimpleLineDividerItemDecoration(context))
        attractionDestinationRecyclerView.adapter = attractionDestinationRecyclerViewAdapter

        activity?.invalidateOptionsMenu()

    }

    private fun observeAttractionDestinations() {

        viewModel.attractionDestinationsLiveData.observe(viewLifecycleOwner, Observer {

            it.getContentIfNotHandled()?.also { resource ->

                when (resource.status) {

                    DataRetrieveStatus.SUCCESS -> {
                        // apply new data
                    }

                    DataRetrieveStatus.RESPONSE_ERROR -> {
                        //Show error
                    }

                    DataRetrieveStatus.CONNECTION_ERROR,
                    DataRetrieveStatus.PROCESSING_ERROR -> {
                        //error handling
                    }
                }
            }
        })
    }

    override fun onItemClick(position: Int) {

        val bundle = Bundle()
        bundle.putParcelable(DESTINATION_DATA_EXTRA, attractionDestinationRecyclerViewAdapter.data[position])

        activity?.invalidateOptionsMenu()

        commonActivityOperations.navigateBackWithResult(DESTINATION_DATA_RESULT, bundle)
        commonActivityOperations.closeSearchBar()
    }

    override fun onQueryChange(text: String) {

        if(networkConnectivity.isOnline){

            viewModel.getAttractionDestinations(text)

        }else{
            // Error handling
        }
    }

    override fun onSearchBarClose() {
        // Reset List
    }

基礎片段

abstract class BaseFragment : Fragment() {

    private lateinit var windowFocusChangeReceiver: BroadcastReceiver

    protected var dialogToDisplayOnWindowIsFocus: MaterialDialog.Builder? = null

    lateinit var commonActivityOperations: CommonActivityOperations

    val networkConnectivity: NetworkStatus by lazy { NetworkStatus(context) }

    override fun onAttach(context: Context) {
        commonActivityOperations = activity as CommonActivityOperations
        super.onAttach(context)
    }
}

AttractionDestinationFragmentTest

class AttractionDestinationFragmentTest {

    private val mockNavController by lazy {
        Mockito.mock(NavController::class.java)
    }

    @Before
    fun setUp() {

        MockitoAnnotations.initMocks(this)

        val fragment = AttractionDestinationFragment()

        launchFragmentInContainer(themeResId = R.style.AppTheme){
            fragment.also { fragment ->
                fragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
                    if (viewLifecycleOwner != null) {
                        Navigation.setViewNavController(fragment.requireView(), mockNavController)
                    }
                }
            }
        }

    }

    @Test
    fun destinationRecyclerViewLoadTest(){

        onView(withId(R.id.attractionDestinationRecyclerView))
                .check(RecyclerViewItemCountAssertion(15))
                .perform(actionOnItemAtPosition<AttractionDestinationRecyclerViewAdapter.ViewHolder>(0, click()))
    }

    @After
    fun tearDown() {
    }

正如你可以看到我已經設置了CommonActivityOperations界面中onAttach(context: Context)BaseFragment 我收到此錯誤是因為FragmentScenario創建了未實現CommonActivityOperations EmptyFragmentActivity並且我正在嘗試啟動它。

是否有另一種方式來啟動這個界面? 如何使這個片段可測試?

謝謝。

編輯:我經歷了以下類似的問題

這個問題沒有解決我的問題,因為我使用的是導航架構組件。 使用導航架構組件時,我不會手動創建片段來將界面傳遞給片段,如本問題所示。

景點流動活動

private fun initialization() {

        navigationController = Navigation.findNavController(this, R.id.hostFragment)
        setSupportActionBar(toolbar)

        val appBarConfiguration = AppBarConfiguration
                .Builder()
                .setFallbackOnNavigateUpListener(this)
                .build()

        NavigationUI.setupActionBarWithNavController(this, navigationController, appBarConfiguration)

    }

上面的代碼顯示了父 Activity 內部的初始化函數,它負責設置導航架構組件並使用工具欄設置它。

如果不引用導航架構組件的活動,我就找不到使用此接口的方法。 我最終做的是使用ReactiveX 我創建了一個類,在值更改時使用PublishSubject通知活動。

通用活動觸發器

class CommonActivityTrigger private constructor() {

    val navigateBackResultPublishSubject: PublishSubject<Pair<String,Bundle>>
            = PublishSubject.create()
    val closeSearchBarPublishSubject: PublishSubject<Boolean>
            = PublishSubject.create()

    private object HOLDER {
        val INSTANCE = CommonActivityTrigger()
    }

    companion object {
        val INSTANCE: CommonActivityTrigger by lazy { HOLDER.INSTANCE }
    }

    fun navigateBackWithResult(resultFor: String, result: Bundle){
        navigateBackResultPublishSubject.onNext(Pair(resultFor,result))
    }

    fun closeSearchBar(){
        closeSearchBarPublishSubject.onNext(true)
    }
}

AttractionDestinationFragment

class AttractionDestinationFragment : BaseFragment(), ItemClickListener, ToolbarSearchChangeListener{

    private lateinit var defaultAttractionList: MutableList<AttractionDestinations>
    private lateinit var attractionDestinationRecyclerViewAdapter: AttractionDestinationRecyclerViewAdapter

    private val viewModel by lazy { getViewModel<AttractionDestinationViewModel>() }
    private val commonActivityTrigger by lazy { CommonActivityTrigger.INSTANCE }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_attraction_destination, container, false)
    }

    override fun onItemClick(position: Int) {

        val bundle = Bundle()
        bundle.putParcelable(DESTINATION_DATA_EXTRA, attractionDestinationRecyclerViewAdapter.data[position])

        activity?.invalidateOptionsMenu()

        commonActivityTrigger.navigateBackWithResult(DESTINATION_DATA_RESULT, bundle)
        commonActivityTrigger.closeSearchBar()
    }
}

AttractionFlow1Activity

class AttractionFlow1Activity : AppCompatActivity(), AppBarConfiguration.OnNavigateUpListener {

    private lateinit var navigationController: NavController

    private val navHostFragment by lazy { supportFragmentManager.findFragmentById(R.id.hostFragment) }
    private val commonActivityTrigger by lazy { CommonActivityTrigger.INSTANCE }

    private val compositeDisposable = CompositeDisposable()

    private fun navigateBackWithResult() {

        val navigationBlackResultPublishDisposable = commonActivityTrigger
                .navigateBackResultPublishSubject
                .subscribe {

                    val (resultFor, result) = it

                    val childFragmentManager = supportFragmentManager
                            .findFragmentById(R.id.hostFragment)?.childFragmentManager
                    var backStackListener: FragmentManager.OnBackStackChangedListener by Delegates.notNull()
                    backStackListener = FragmentManager.OnBackStackChangedListener {
                        (childFragmentManager?.fragments?.get(0) as NavigationResult).onNavigationResult(resultFor, result)
                        childFragmentManager.removeOnBackStackChangedListener(backStackListener)
                    }
                    childFragmentManager?.addOnBackStackChangedListener(backStackListener)
                    navigationController.popBackStack()
                }

        compositeDisposable.add(navigationBlackResultPublishDisposable)

    }

    override fun onDestroy() {
        compositeDisposable.dispose()
        super.onDestroy()
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM