【问题标题】:Robolectric test fails when trying to inflate layout that has a TextInputLayout尝试膨胀具有 TextInputLayout 的布局时,Robolectric 测试失败
【发布时间】:2020-09-25 20:22:44
【问题描述】:
Android Studio 4.0.1
Robolectric 4.3.1

我正在编写一个 robolectric 测试,测试将失败并出现以下错误。并且似乎与不能膨胀的TextInputLayout有关。如果我删除 TextInputLayout,测试将通过。

这是我收到的错误消息。

android.view.InflateException: Binary XML file line #47: Binary XML file line #47: Error inflating class com.google.android.material.textfield.TextInputLayout
Caused by: android.view.InflateException: Binary XML file line #47: Error inflating class com.google.android.material.textfield.TextInputLayout
Caused by: java.lang.IllegalArgumentException: The style on this component requires your app theme to be Theme.AppCompat (or a descendant).

测试类本身

@Config(sdk = [Build.VERSION_CODES.O_MR1])
@RunWith(AndroidJUnit4::class)
class CharitiesAdapterTest {

    private lateinit var charitiesAdapter: CharitiesAdapter

    @Before
    fun setUp() {
        charitiesAdapter = CharitiesAdapter()
    }

    @Test
    fun `should create viewHolder`() {
        // Act & Assert
        assertThat(createViewHolder()).isNotNull
    }

    private fun createViewHolder(): CharitiesViewHolder {
        val constraintLayout = ConstraintLayout(ApplicationProvider.getApplicationContext())

        return charitiesAdapter.onCreateViewHolder(constraintLayout, 0)
    }
}

实际的被测适配器

class CharitiesAdapter : RecyclerView.Adapter<CharitiesViewHolder>() {

    private val charitiesList: MutableList<Charity> = mutableListOf()
    private var selectedCharity: (Charity) -> Unit = {}

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CharitiesViewHolder {
        val charitiesViewHolder = CharitiesViewHolder(
            LayoutInflater.from(parent.context).inflate(R.layout.charity_item, parent, false))

        return charitiesViewHolder
    }

    override fun getItemCount(): Int = charitiesList.count()

    override fun onBindViewHolder(holder: CharitiesViewHolder, position: Int) {
        // left blank
    }
}

这是有问题的布局部分。仅删除 TextInputLayout 时,测试将通过 OK

 <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputLayoutPostcode"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/ivLogo">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/editTextPostcode"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </com.google.android.material.textfield.TextInputLayout>
    

这是我正在使用的样式

    <resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
</resources>

感谢您的建议

【问题讨论】:

    标签: android robolectric android-textinputlayout instrumented-test


    【解决方案1】:

    您可以将测试配置为使用模拟应用程序,该应用程序使用如下所示的应用主题:

    Kotlin 实现

    class TestApplication : Application() {
    
        override fun onCreate() {
            super.onCreate()
            setTheme(R.style.AppTheme) //or just R.style.Theme_AppCompat
        }
    }
    
    

    然后配置您的测试以使用这个模拟应用程序

    @Config(application=TestApplication::class, sdk = [Build.VERSION_CODES.O_MR1])
    @RunWith(AndroidJUnit4::class)
    class CharitiesAdapterTest {
    

    Java 实现

    public class TestApplication extends Application {
    
        @Override
        public void onCreate() {
            super.onCreate();
            setTheme(R.style.AppTheme); //or just R.style.Theme_AppCompat
        }
    }
    
    

    然后配置您的测试以使用这个模拟应用程序

    @Config(application=TestApplication.class, sdk = [Build.VERSION_CODES.O_MR1])
    @RunWith(AndroidJUnit4.class)
    class CharitiesAdapterTest {
    

    【讨论】:

    • 不确定为应用程序设置主题是否会改变任何东西,因为它没有视图(应用程序没有主题,因为它不需要没有任何视图的主题来设置样式)。 R.style.AppTheme 是所有 Activity 的默认主题,但不是 Application
    • 那么如果有两个Activity,一个使用Theme.MaterialComponents.Light.DarkActionBarNoActionBar,另一个使用不起作用的主题。您的测试方法如何能够检测到问题?请参阅AndroidManifest.xml 中定义的实际主题测试方法。
    • 感谢您的后续问题,我在本地进行了测试,在发布之前进行了修改和不修改,以确保它有效。它似乎只需要主题而不需要活动。
    • 他正在测试慈善适配器,而不是适配器在特定活动中的工作方式。他可以为该活动编写一个单独的测试,该测试将揭示与该活动相关的任何问题以及适配器在活动中的执行方式,从而实现关注点分离。
    • @ggordon 没错。我只是将 CharitiesAdapter 作为独立单元测试进行测试。我已经删除了将视图绑定到一些模拟数据的不重要逻辑,因为我想让 sn-ps 对于这个问题保持简短。我以前用其他适配器进行过这样的测试。但是,我从未测试过布局中包含 TextInputLayout 的适配器。
    【解决方案2】:

    您传递了错误的Context 类:ApplicationProvider.getApplicationContext()

    ApplicationContext 根本没有主题,建议使用ActivityContext(无论是Activity 还是Theme,它都将始终具有当前Activity 的主题)。根据提供的代码,不清楚您如何启动Activity(那里应该可以访问Context)。

    使用ActivityScenarioActivityScenarioRule 进行测试可能是正确的方法。
    文档显示了它的工作原理:AndroidX TestJUnit4 rules with AndroidX Test

    【讨论】:

    • 这只是一个recyclerview适配器测试。所以我实际上并没有开始任何活动,只是使用 robolectric 来膨胀布局。谢谢
    • 嘲笑Context 是有问题的,因为即使应用程序崩溃,测试也可能通过。在运行集成测试时,假设适配器工作正常,但有可用的项目。当测试多个组件时(您会这样做),这不再是单元测试(虽然对 Google 的库进行单元测试毫无意义,这些库通常已经过良好测试)。当您测试此类组件的自定义实现时,这将是完全不同的事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多