Espresso 测试期间出现 NoMatchingViewException 错误

问题描述 投票:0回答:1

我正在对Fragment是否显示进行仪器测试。当我开始执行测试时,我可以在物理设备中看到 FragmentLogin 视图,但测试失败,并显示 androidx.test.espresso.NoMatchingViewException: 在层次结构中找不到匹配的视图:view.getId() 是 <2131296460/com.nano.modularapp:id/fragmentLogin> 并且应用程序关闭.

我在代码中使用了 Hilt 和 Navigation Graph 进行测试。

主要活动::类

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private lateinit var navController: NavController
    private lateinit var toolbar: Toolbar

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        toolbar = findViewById(R.id.toolbar)
        //https://medium.com/androiddevelopers/navigationui-d21fd4f5c318
        val navHostFragment = supportFragmentManager.findFragmentById(R.id.navHostFragmentController) as NavHostFragment
        navController = navHostFragment.navController
        val appBarConfiguration = AppBarConfiguration(navController.graph)
        setSupportActionBar(toolbar)
        setupActionBarWithNavController(navController,appBarConfiguration)
    }
}

MainActivity.xml

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/navHostFragmentController"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/toolbar"
        app:layout_constraintBottom_toBottomOf="parent"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/onboard_graph"
        app:defaultNavHost="true"/>

</androidx.constraintlayout.widget.ConstraintLayout>

导航图

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/onboard_graph"
    app:startDestination="@id/fragmentLogin">

    <fragment
        android:id="@+id/fragmentLogin"
        android:name="com.nano.modularapp.login.FragmentLogin"
        android:label="Login" >
        <action
            android:id="@+id/action_fragmentLogin_to_fragmentSignUp"
            app:destination="@id/fragmentSignUp" />
    </fragment>
    <fragment
        android:id="@+id/fragmentSignUp"
        android:name="com.nano.modularapp.signup.FragmentSignUp"
        android:label="Sign Up" />
</navigation>

FragmentLogin::类

@AndroidEntryPoint
class FragmentLogin : Fragment() {

    private lateinit var binding: FragmentLoginBinding
    private val viewModel by viewModels<LoginViewModel>()
    var errorMsg: ObservableField<String> = ObservableField("")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate(inflater,R.layout.fragment_login,container,false)
        binding.login = this
        binding.lifecycleOwner = this
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        observeInputFieldsError()
        setListener()
    }

    private fun observeInputFieldsError(){
        viewModel.inputFieldError.observe(viewLifecycleOwner, Observer { state->
            when(state){
                is ValidationState.EmailError->{
                    errorMsg.set(getString(state.error))
                }
                is ValidationState.PasswordError->{
                    errorMsg.set(getString(state.error))
                }
                ValidationState.Success->{}
            }
        })
    }

    private fun setListener(){

        binding.navigateToSignUp.setOnClickListener {
            findNavController().navigate(R.id.action_fragmentLogin_to_fragmentSignUp)
        }

        binding.btnLogin.setOnClickListener {
            viewModel.loginUser("","")
        }
    }
}

FragmentLogin.xml

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="login"
            type="com.nano.modularapp.login.FragmentLogin" />

    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".login.FragmentLogin">

        <TextView
            android:id="@+id/tvSignUpTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Welcome in ModularApp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginTop="20dp"
            android:layout_marginStart="20dp"
            android:textColor="@color/black"
            android:fontFamily="@font/mukta_bold"
            android:textSize="26sp"
            android:textStyle="bold"/>


        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/tvSignUpTitle"
            app:layout_constraintStart_toStartOf="parent"
            android:text="Login"
            android:textColor="@color/black"
            android:layout_marginTop="20dp"
            android:layout_marginStart="20dp"
            android:fontFamily="@font/mukta_medium"
            android:textSize="22sp" />

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_margin="20dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Email"
            android:textSize="20sp"
            android:layout_marginTop="15dp"
            android:textColor="@color/text_color"
            android:fontFamily="@font/mukta_medium"/>

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextEmail"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Email"
            android:layout_marginTop="5dp"
            android:textColor="@color/text_color"
            android:fontFamily="@font/mukta_regular"
            android:textSize="18sp"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Password"
            android:textSize="20sp"
            android:layout_marginTop="20dp"
            android:textColor="@color/text_color"
            android:fontFamily="@font/mukta_medium"
            android:inputType="textEmailAddress"/>

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextPassword"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Password"
            android:layout_marginTop="5dp"
            android:textColor="@color/text_color"
            android:fontFamily="@font/mukta_regular"
            android:textSize="18sp"
            android:inputType="textPassword"/>

        <TextView
            android:id="@+id/tvError"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="15sp"
            android:layout_marginTop="15dp"
            android:fontFamily="@font/mukta_regular"
            android:text="@{login.errorMsg}"
            android:textColor="@color/error"/>

        <Button
            android:id="@+id/btnLogin"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Login"
            android:textSize="20sp"
            android:paddingStart="20dp"
            android:paddingEnd="20dp"
            android:layout_marginTop="25dp"
            android:layout_gravity="center"
            android:fontFamily="@font/mukta_semi_bold"/>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:padding="8dp"
                android:gravity="center">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textSize="15sp"
                    android:text="Don't have an account?"
                    android:textColor="@color/black"
                    android:fontFamily="@font/mukta_regular"/>

                <TextView
                    android:id="@+id/navigateToSignUp"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textSize="18sp"
                    android:text="  Sign Up"
                    android:textColor="@color/info_blue"
                    android:fontFamily="@font/mukta_medium"/>

            </LinearLayout>

        </LinearLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

FragmentLoginInstrumentedTest::类

@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
class FragmentLoginInstrumentedTest {

    @get:Rule
    val hiltRule:HiltAndroidRule = HiltAndroidRule(this)

    @get:Rule
    val activityRule: ActivityScenarioRule<MainActivity> = ActivityScenarioRule(MainActivity::class.java)

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

    @Test
    fun test_blankField_expectedEmptyField(){
        //Checking LoginFragment is attached and displayed in screen or not
        onView(withId(R.id.fragmentLogin)).check(ViewAssertions.matches(isDisplayed()))

        //Clicking on Sign Up text to navigate to Sign Up Fragment
        //onView(withId(R.id.navigateToSignUp)).perform(ViewActions.click())

        //Checking SignUpFragment is displayed or not
        //onView(withId(R.id.fragmentSignUp)).check(ViewAssertions.matches(isDisplayed()))

        //Clicking Sign Up Button
        //onView(withId(R.id.btnSignUp)).perform(ViewActions.click())

        //Checking empty email error message
        //onView(withId(R.id.tvError)).check(ViewAssertions.matches(withText("Email field is empty")))
    }

}

以上是我的完整代码。我不明白原因,登录片段显示然后测试用例失败。

android junit4 android-espresso dagger-hilt instrumented-test
1个回答
0
投票

我能够解决这个问题。一个很小的点需要看这里

当您使用 Hilt + Navigation 使用 NavController 加载片段时。 Android Instrumentation 测试过程中需要考虑的几点

  1. 运行仪器测试注释时 @RunwWith(AndroidJUnitRunner::class)

  2. 设置另一个规则来确定您将启动 Fragment 的 Activity。正如你 正在使用片段,您需要在调试主中创建 HiltTestActivity java目录中的文件夹。

  3. List 编写时 Espresso.onView(withId(R.id.loginFragment)) --> 将其替换为 Espresso.onView(withId(R.id.loginFragmentView))。 “loginFragmentView”您需要将其添加到片段的根视图中 布局文件名。

请查看这个 github 解决方案供您参考 NoMatchingViewException 问题

© www.soinside.com 2019 - 2024. All rights reserved.