微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

包含自定义视图的 Android UI 测试 Fragment

如何解决包含自定义视图的 Android UI 测试 Fragment

我需要测试一个包含自定义视图的片段。我是编写 UI 测试的新手,但遇到了一个错误

android.view.InflateException: Binary XML file line #36 in *****.staging.debug:layout/fragment_form: Binary XML file line #36 in *****.staging.debug:layout/fragment_form: Error inflating class *****.util.view.BrandedEditText
Caused by: android.view.InflateException: Binary XML file line #36 in *****.staging.debug:layout/fragmentn_form: Error inflating class *****.util.view.BrandedEditText
Caused by: java.lang.NoSuchMethodException: *****.util.view.BrandedEditText.<init> [class android.content.Context,interface android.util.AttributeSet]

调用 launchFragmentInContainer 时发生错误,然后触发 setAndBindContentViewBaseFragment

我不明白为什么会出现这个错误,但原因可能很简单。有人可以帮我吗?

测试如下:

import androidx.core.os.bundleOf
import androidx.fragment.app.testing.launchFragment
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.navigation.fragment.navArgs
import androidx.test.espresso.Espresso
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import *****.R
import *****.ui.MainActivity
import org.junit.Assert
import org.junit.Rule
import org.junit.Test

import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
internal class FormFragmentTest {

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

    @Test
    fun first_check() {
        val fragmentArgs = bundleOf("userId" to 0)

        // Todo replace with launchFragment
        val scenario = launchFragmentInContainer<FormFragment>(fragmentArgs)

        scenario.onFragment { fragment ->
            val args: FormFragmentArgs by fragment.navArgs()
            Assert.assertEquals(args.userId,0)
        }
    }
}

表单片段:

class FormFragment : BaseFragment(),Injectable {

    private val loadingDialog by lazy { LoadingDialog(requireContext()) }

    @Inject
    lateinit var viewmodelFactory: DaggerviewmodelFactory<Formviewmodel>

    private val viewmodel: Formviewmodel by lazy {
        viewmodelProvider(
                this,viewmodelFactory
        ).get(Formviewmodel::class.java)
    }

    override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View {
        val binding: FragmentFormBinding = setAndBindContentView(inflater,container,R.layout.fragment_form)
        binding.viewmodel = viewmodel
        return binding.root
    }

...

基础片段:

abstract class BaseFragment : Fragment(),NavControllerOwner {

    protected var binding: ViewDataBinding? = null
    protected var isAlreadyBound = false

    protected val mainActivity: MainActivity?
        get() = activity as? MainActivity

    override val navController: NavController?
        get() = (activity as NavControllerOwner).navController

    @Suppress("UNCHECKED_CAST")
    protected fun <ViewBindingType : ViewDataBinding?> setAndBindContentView(
            inflater: LayoutInflater,@LayoutRes layoutResId: Int
    ): ViewBindingType {
        binding?.let {
            isAlreadyBound = true
            binding?.lifecycleOwner = viewLifecycleOwner
            return it as ViewBindingType
        }
        binding = DataBindingUtil.inflate(inflater,layoutResId,false)
        binding?.lifecycleOwner = viewLifecycleOwner
        return binding as ViewBindingType
    }

    protected fun setContentView(
            inflater: LayoutInflater,@LayoutRes layoutResID: Int
    ): View = inflater.inflate(layoutResID,false)
}

自定义视图:

import android.content.Context
import android.os.Parcelable
import android.text.InputFilter
import android.text.InputFilter.LengthFilter
import android.text.InputType
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import androidx.annotation.StringRes
import androidx.appcompat.widget.AppCompatEditText
import androidx.databinding.BindingAdapter
import com.google.android.material.textfield.TextInputLayout
import *****.R
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.view_branded_edit_text.view.*

/**
 * Styled edit text.  Use android:inputType for input type,and app:editTextTitle for the title.
 */
class BrandedEditText(context: Context,attrs: AttributeSet) : FrameLayout(context,attrs) {

    val editText: AppCompatEditText
        get() = brandedAppCompatEditText

    val inputLayout: TextInputLayout
        get() = textInputLayout

    init {
        View.inflate(context,R.layout.view_branded_edit_text,this)
        context.theme.obtainStyledAttributes(
                attrs,R.styleable.BrandedEditText,0
        ).apply {
            title.text = getString(R.styleable.BrandedEditText_editTextTitle)
            brandedAppCompatEditText.inputType = getInt(R.styleable.BrandedEditText_android_inputType,InputType.TYPE_NULL)
            brandedAppCompatEditText.filters = arrayOf<InputFilter>(
                    LengthFilter(getInt(R.styleable.BrandedEditText_maxLength,Int.MAX_VALUE))
            )
            if (getBoolean(R.styleable.BrandedEditText_passwordToggleEnabled,false)) {
                inputLayout.endIconMode = TextInputLayout.END_ICON_PASSWORD_TOGGLE
            }
        }
    }

    fun setMaxLength(maxLength: Int) {
        brandedAppCompatEditText.filters = arrayOf<InputFilter>(
                LengthFilter(maxLength)
        )
    }

    override fun onSaveInstanceState(): Parcelable? = SavedState(super.onSaveInstanceState(),editText.text.toString())

    override fun onRestoreInstanceState(state: Parcelable?) {
        super.onRestoreInstanceState(state)
        editText.setText((state as? SavedState)?.text)
    }

    companion object {
        @JvmStatic
        @BindingAdapter("app:errorText")
        fun setErrorText(view: BrandedEditText,@StringRes textId: Int) {
            if (textId == 0x0) return
            view.inputLayout.error = view.context.getString(textId)
        }
    }

    @Parcelize
    data class SavedState(
            val superStateValue: Parcelable?,val text: String?
    ) : BaseSavedState(superStateValue)
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。