繁体   English   中英

FragmentScenario 必须实现 OnFragmentInteractionListener

[英]FragmentScenario must implement OnFragmentInteractionListener

我目前正在尝试使用本教程中建议的 androidTest、mokito 和 espresso 测试 android 中的导航: https : //developer.android.com/guide/navigation/navigation-testing但我系统地收到以下错误: E/MonitoringInstr : 遇到的异常: Thread[main,5,main]。 将线程状态转储到输出并固定到峡湾。 java.lang.RuntimeException: androidx.fragment.app.testing.FragmentScenario$EmptyFragmentActivity@fa3e8be 必须实现 OnFragmentInteractionListener

这是测试类:

package developer.android.com.enlightme

import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.navigation.NavController
import androidx.navigation.Navigation
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.filters.MediumTest
import androidx.test.runner.AndroidJUnit4
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.*


@MediumTest
@RunWith(AndroidJUnit4::class)
class MainFragmentTest{
    @Test
    fun createDebateTransition(){
        // Create a mock NavController
        val mockNavController = mock(NavController::class.java)
        // Create a graphical FragmentScenario for the TitleScreen
        var mainScenario = launchFragmentInContainer<MainFragment>()
        // Set the NavController property on the fragment
        mainScenario.onFragment { fragment ->  Navigation.setViewNavController(fragment.requireView(), mockNavController)
        }
        // Verify that performing a click "créer" prompt the correct Navigation action
        onView(ViewMatchers.withId(R.id.nav_button_creer)).perform(ViewActions.click())
        verify(mockNavController).navigate(R.id.action_mainFragment_to_create1Fragment)
    }
  }
}

这是我的片段

package developer.android.com.enlightme

import android.content.Context
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.navigation.findNavController


/**
 * A simple [Fragment] subclass.
 * Activities that contain this fragment must implement the
 * [MainFragment.OnFragmentInteractionListener] interface
 * to handle interaction events.
 * Use the [MainFragment.newInstance] factory method to
 * create an instance of this fragment.
 *
 */
class MainFragment : Fragment() {
    private var listener: OnFragmentInteractionListener? = null

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val binding = DataBindingUtil.inflate<developer.android.com.enlightme.databinding.FragmentMainBinding>(inflater, R.layout.fragment_main, container, false)
        setHasOptionsMenu(true)
        //Click listener to create fragment
        binding.navButtonCreer.setOnClickListener { view : View ->
            view.findNavController().navigate(R.id.action_mainFragment_to_create1Fragment)
        }
        binding.navButtonRejoindre.setOnClickListener{view : View ->
            view.findNavController().navigate(R.id.action_mainFragment_to_joinDebateFragment)
        }
        return binding.root
    }
    fun onButtonPressed(uri: Uri) {
        listener?.onFragmentInteraction(uri)
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is OnFragmentInteractionListener) {
            listener = context
        } else {
            throw RuntimeException(context.toString() + " must implement OnFragmentInteractionListener")
        }
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }

    /**
     * This interface must be implemented by activities that contain this
     * fragment to allow an interaction in this fragment to be communicated
     * to the activity and potentially other fragments contained in that
     * activity.
     *
     *
     * See the Android Training lesson [Communicating with Other Fragments]
     * (http://developer.android.com/training/basics/fragments/communicating.html)
     * for more information.
     */
    interface OnFragmentInteractionListener {
        fun onFragmentInteraction(uri: Uri)
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @return A new instance of fragment MainFragment.
         */
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
            MainFragment().apply {
                arguments = Bundle().apply {
                }
            }
    }
}

和 mainAcrivity 文件:

package developer.android.com.enlightme

import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.findNavController
import androidx.navigation.ui.NavigationUI
import developer.android.com.enlightme.Debate.*
import developer.android.com.enlightme.Debate.ConcurentOp.InsertArg
import developer.android.com.enlightme.databinding.ActivityMainBinding
import developer.android.com.enlightme.objects.DebateEntity

class MainActivity : AppCompatActivity(), MainFragment.OnFragmentInteractionListener,
    Create1Fragment.OnFragmentInteractionListener,
    Create2Fragment.OnFragmentInteractionListener,
    DebateFragment.OnFragmentInteractionListener,
    ArgumentPlusSide1Fragment.OnFragmentInteractionListener,
    ArgumentPlusSide2Fragment.OnFragmentInteractionListener,
    ArgumentSide1Fragment.OnFragmentInteractionListener,
    ArgumentSide2Fragment.OnFragmentInteractionListener,
    NewArgDialogFragment.OnFragmentInteractionListener,
    JoinDebateFragment.OnFragmentInteractionListener,
    ItemBtListFragment.OnFragmentInteractionListener,
    ProvideUserNameFragment.OnFragmentInteractionListener,
    NewArgDialogFragment.NoticeDialogListener,
    ProvideUserNameFragment.NoticeDialogListener{

    private lateinit var binding: ActivityMainBinding
    private lateinit var debateViewModel: DebateViewModel
    private lateinit var joinDebateViewModel: JoinDebateViewModel
    lateinit var debateFragment: DebateFragment

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        val navController = this.findNavController(R.id.myNavHostFragment)
        NavigationUI.setupActionBarWithNavController(this, navController)
        debateViewModel = this.run {
            ViewModelProviders.of(this).get(DebateViewModel::class.java)
        }
        joinDebateViewModel = this.run {
            ViewModelProviders.of(this).get(JoinDebateViewModel::class.java)
        }
    }
    override fun onSupportNavigateUp(): Boolean {
        val navController = this.findNavController(R.id.myNavHostFragment)
        return navController.navigateUp()
    }
    override fun onFragmentInteraction(uri: Uri) {
    }
    // The dialog fragment receives a reference to this Activity through the
    // Fragment.onAttach() callback, which it uses to call the following methods
    // defined by the NoticeDialogFragment.NoticeDialogListener interface
    override fun onDialogPositiveClick(dialog: DialogFragment) {
        if(debateViewModel.edit_arg_pos >= 0){
            debateFragment.modArgument(debateViewModel.temp_side,
                debateViewModel.temp_debate_entity, debateViewModel.edit_arg_pos)
        }else{
            val place : Int
            if(debateViewModel.temp_side == 1){
                place = debateViewModel.debate.value?.get_debate_entity()?.side_1_entity?.size ?: -1
            }else{
                place = debateViewModel.debate.value?.get_debate_entity()?.side_2_entity?.size ?: -1
            }
            val currDebate = this.debateViewModel.debate.value?.get_debate_entity()
            if (currDebate != null){
                val operation = InsertArg(debateViewModel.temp_debate_entity, place, debateViewModel.temp_side)
                debateViewModel.debate.value?.manageUserUpdate(listOf(operation), this,
                    joinDebateViewModel.listEndpointId, joinDebateViewModel.myEndpointId, currDebate.path_to_root)
            }

        }
        debateViewModel.temp_side = 0
        debateViewModel.temp_debate_entity = DebateEntity()
    }
    override fun onDialogNegativeClick(dialog: DialogFragment) {
        // User touched the dialog's negative button
    }
}

和模块graddle文件:

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'
apply plugin: 'maven'
apply plugin: 'kotlin-android-extensions'
apply plugin: "androidx.navigation.safeargs"
apply plugin: "kotlinx-serialization"

android {
    compileSdkVersion 28
    dataBinding {
        enabled = true
    }
    defaultConfig {
        applicationId "developer.android.com.enlightme"
        minSdkVersion 18
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables.useSupportLibrary = true
        android.defaultConfig.vectorDrawables.useSupportLibrary = true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    buildToolsVersion = '28.0.3'
    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.2.0-alpha01'
    implementation 'androidx.core:core-ktx:1.2.0-rc01'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'

    //ViewModel
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-rc03'
    //MaterialIO
    implementation 'com.google.android.material:material:1.2.0-alpha03'

    // Navigation

    // Java
    implementation "androidx.navigation:navigation-fragment:$navigationVersion"
    implementation "androidx.navigation:navigation-ui:$navigationVersion"

    // Kotlin
    implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
    implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"

    //P2P with Nearby
    implementation "com.google.android.gms:play-services-nearby:17.0.0"

    //Serialization
    implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.13.0" // JVM dependency

    //Testing
    // Required -- JUnit 4 framework
    testImplementation 'junit:junit:4.13-rc-2'
    // Optional -- Robolectric environment
    testImplementation 'androidx.test:core:1.2.0'
    // Optional -- Mockito framework
    //testImplementation 'org.mockito:mockito-core:1.10.19'
    //androidTestImplementation "org.mockito:mockito-core:${var}"
    //espresso
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
    androidTestImplementation 'androidx.test:runner:1.1.0'
    androidTestImplementation 'androidx.test:rules:1.1.0'
    //androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
    debugImplementation "androidx.fragment:fragment-testing:$fragmentVersion"
    androidTestImplementation "org.mockito:mockito-core:${var}"
    androidTestImplementation "com.google.dexmaker:dexmaker:1.2"
    androidTestImplementation "com.google.dexmaker:dexmaker-mockito:1.2"


}

和项目梯度文件:

ext {
    espressoVersion = '3.3.0-alpha03'
    coroutinesVersion = '1.2.1'
    fragmentVersion = '1.1.0'
    var = '1.10.19'
    var1 = '1.3.60'
    kotlin_version = '1.3.60'
}// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ext {
    kotlin_version = '1.3.40'
        navigationVersion = '2.2.0-rc04'
    }
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.3'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
        //navigation
        def nav_version = "2.1.0-alpha05"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

我正在使用 Android Studio 3.5.3。

所以基本上,当我在手机上运行应用程序(包括导航)时,一切都很好。 问题源于仪器化测试本身。 据我了解,FragmentScenario 没有实现 OnFragmentInteractionListener。 我当然不能改变 FragmentScenario 类,我不知道应该如何管理这个东西。 我是否使用错误的工具来测试片段交互?

谢谢你们!

FragmentScenario 获取您的片段并在容器中启动它,使用默认的空活动作为主机。 它不会启动您的活动。 目标是单独测试片段。

当然,该空主机活动没有实现 OnFragmentInteractionListener,因为它是您创建的接口。 在您的 onAttach 回调中,您强制主机活动实现此接口并告诉它否则抛出异常。 这就是您在测试期间遇到的错误。

您可以删除 onAttach 方法中的 else 部分,错误就会消失。 但是您的侦听器将为空,并且某些依赖于它的功能将无法正常工作。

也许你也可以考虑改变这个架构。 您可以考虑使用共享视图模型吗? 这比与听众互动更容易。 如果您不想更改当前状态,您可以继续进行集成测试而不是孤立的片段测试。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM