FragmentScenario 配置 - 二进制 XML 文件行,使用 espresso 测试时错误膨胀 class <widget>

FragmentScenario configuration - Binary XML file line, Error inflating class <widget> while testing with espresso

如果在 XML 中使用 material 组件,则尝试将 FragmentScenariolaunchFragmentlaunchFragmentInContainer 一起使用时出现膨胀错误。

android.view.InflateException: Binary XML file line #41: Binary XML file line #41: Error inflating class <widget class>
Caused by: android.view.InflateException: Binary XML file line #41: Error inflating class <widget class>
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:334)
at android.view.LayoutInflater.createView(LayoutInflater.java:647)
at com.android.internal.policy.PhoneLayoutInflater.onCreateView(PhoneLayoutInflater.java:58)
at android.view.LayoutInflater.onCreateView(LayoutInflater.java:720)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:788)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:863)
at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
...
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:886)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1227)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1293)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:710)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2063)
at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1853)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1808)
at androidx.fragment.app.FragmentManagerImpl.execSingleAction(FragmentManagerImpl.java:1685)
at androidx.fragment.app.BackStackRecord.commitNow(BackStackRecord.java:554)
at androidx.fragment.app.testing.FragmentScenario.perform(FragmentScenario.java:308)
at androidx.fragment.app.testing.FragmentScenario.perform(FragmentScenario.java:286)
at androidx.test.core.app.ActivityScenario.lambda$onActivity$ActivityScenario(ActivityScenario.java:534)
at androidx.test.core.app.ActivityScenario$$Lambda[=13=].run(Unknown Source:4)
at android.app.Instrumentation$SyncRunnable.run(Instrumentation.java:2093)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Caused by: java.lang.UnsupportedOperationException: Failed to resolve attribute at index 5: TypedValue{t=0x2/d=0x7f03009f a=-1}
at android.content.res.TypedArray.getColorStateList(TypedArray.java:538)
at android.widget.TextView.<init>(TextView.java:1214)
at android.widget.Button.<init>(Button.java:166)
at android.widget.Button.<init>(Button.java:141)
at android.widget.Button.<init>(Button.java:117)

无法膨胀 XML 因为缺少样式。

首先一定要有最新的fragment-testing依赖:

debugImplementation "androidx.fragment:fragment-testing:$fragment_version"

一些附加信息:

  1. 此修复程序已在版本 1.1.0-alpha03 中发布,因此之前的版本将无法像 here 所述那样工作。
  2. 记得使用 debugImplementation 否则依赖项将无法正常工作,感谢这个 so answer
  3. 如果您遇到类似 "process crashed, "No tests found."" 的错误,请检查此 issue 是否有帮助。

在此之后您可以创建片段:

val bundle = Bundle()
.... 
launchFragmentInContainer(bundle, R.style.Theme_AppCompat) {
    YourFragment()
}
//proceed here with espresso testing

Don't forget R.style.Theme_AppCompat otherwise Espresso will crash with the android.view.InflateException error if you use widgets coming from the com.google.android.material:material artifact. Obviously if you need a custom style you can add a new rule to your styles.xml and reference it here.

在我的例子中,我有 Navigation component configured so I had to follow the suggestion of the official documentation here 来确保 navController 的生命周期正常。

首先,我在 TestFragmentUtils.kt

中创建了一个通用方法
inline fun <reified F : Fragment> launchFragmentScenario(
    bundle: Bundle?, fragment: F, navController: NavController): FragmentScenario<F> {
    return launchFragmentInContainer(bundle, R.style.Theme_AppCompat) {
        fragment.also { fragment ->
            fragment.viewLifecycleOwnerLiveData.observeForever { lifeCycleOwner ->
                if (lifeCycleOwner != null) {
                    // The fragment’s view has just been created
                    Navigation.setViewNavController(fragment.requireView(), navController)
                }
            }
        }
    }
}

然后在我的 YourFragmentTest 中,我可以创建另一个方法,例如:

private fun launchMyFragmentScenario(bundle: Bundle?): FragmentScenario<MyFragment> 
    //viewModel factory can be easily injected if you use FragmentFactory
    = TestFragmentUtils.launchFragmentScenario(bundle, MyFragment(viewModelFactory), navController)

并在每次测试开始前调用它。 (navController 参数在@Before 方法中被模拟)