如何解决包含自定义视图的 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
时发生错误,然后触发 setAndBindContentView
的 BaseFragment
。
我不明白为什么会出现这个错误,但原因可能很简单。有人可以帮我吗?
测试如下:
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 举报,一经查实,本站将立刻删除。