我正在嘗試為使用 ViewBinding 的 RecyclerView.ViewHolder 類撰寫單元測驗,但我遇到了在我的測驗類中膨脹我的 ViewBinding 的問題,在運行我的測驗時出現此錯誤:
Binary XML file line #38: Binary XML file line #38: Error inflating class <unknown> Caused by: java.lang.UnsupportedOperationException: Failed to resolve attribute at index 5: TypedValue{t=0x2/d=0x7f04015d a=2}
我在測驗類中找不到 ViewBinding inflate 的代碼示例,這可能嗎?我找到了這個 StackOverflow 執行緒,但它使用 PowerMock 來模擬 ViewBinding 類。我在我的專案中使用了 mockK,我認為在我的情況下使用真正的 ViewBinding 實體會更好。
我的 ViewHolder 看起來像這樣:
class MemoViewHolder(private val binding: MemoItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(data: Memo) {
with(binding) {
// doing binding with rules I would like to test
}
}
}
我的測驗課是這樣的。我正在使用MockK和Robolectric來獲取應用程式背景關系
@RunWith(RobolectricTestRunner::class)
class MemoViewHolderTest {
private lateinit var context: MyApplication
@Before
fun setUp() {
MockKAnnotations.init(this)
context = ApplicationProvider.getApplicationContext()
}
@Test
fun testSuccess() {
val viewGroup = mockk<ViewGroup>(relaxed = true)
val binding = MemoItemBinding.inflate(LayoutInflater.from(context), viewGroup, false)
}
}
編輯:這是@tyler-v 答案的 mockK 版本
@RelaxedMockK
private lateinit var layoutInflater: LayoutInflater
@RelaxedMockK
private lateinit var rootView: ConstraintLayout // must be the type of the root view in the layout
@RelaxedMockK
private lateinit var groupView: ViewGroup
// mock every views in your layout
@RelaxedMockK
private lateinit var title: TextView
@Before
fun setUp() {
context = ContextThemeWrapper(
ApplicationProvider.getApplicationContext<MyApplication>(),
R.style.AppTheme
)
MockKAnnotations.init(this)
every { layoutInflater.inflate(R.layout.memo_item, groupView, false) } returns rootView
every { rootView.childCount } returns 1
every { rootView.getChildAt(0) } returns rootView
// mock findViewById for each view in the memo_item layout
every { rootView.findViewById<TextView>(R.id.title) } returns title
}
@After
fun tearDown() {
unmockkAll()
}
@Test
fun testBindUser() {
val binding = MemoItemBinding.inflate(layoutInflater, groupView, false)
MemoListAdapter.MemoViewHolder(binding).bind(memoList[0])
// some tests...
}
uj5u.com熱心網友回復:
我能夠通過查看生成的系結類來查看我需要模擬哪些方法以使其膨脹并正確回傳模擬視圖,從而使其作業(使用Mockito,但它也應該適用于MockK)。這些檔案app/build/generated/data_binding_base_class_source_out/debug/out/your/package/databinding用于標準構建
這是在 ConstraintLayout 中具有三個視圖的生成資料系結類的示例。
public final class ActivityMainBinding implements ViewBinding {
@NonNull
private final ConstraintLayout rootView;
@NonNull
public final Button getText;
@NonNull
public final ProgressBar progress;
@NonNull
public final TextView text;
private ActivityMainBinding(@NonNull ConstraintLayout rootView, @NonNull Button getText,
@NonNull ProgressBar progress, @NonNull TextView text) {
this.rootView = rootView;
this.getText = getText;
this.progress = progress;
this.text = text;
}
@Override
@NonNull
public ConstraintLayout getRoot() {
return rootView;
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, null, false);
}
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.activity_main, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
@NonNull
public static ActivityMainBinding bind(@NonNull View rootView) {
// The body of this method is generated in a way you would not otherwise write.
// This is done to optimize the compiled bytecode for size and performance.
int id;
missingId: {
id = R.id.get_text;
Button getText = ViewBindings.findChildViewById(rootView, id);
if (getText == null) {
break missingId;
}
id = R.id.progress;
ProgressBar progress = ViewBindings.findChildViewById(rootView, id);
if (progress == null) {
break missingId;
}
id = R.id.text;
TextView text = ViewBindings.findChildViewById(rootView, id);
if (text == null) {
break missingId;
}
return new ActivityMainBinding((ConstraintLayout) rootView, getText, progress, text);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
}
為了能夠在單元測驗中呼叫 inflate 并讓系結保持模擬視圖,您需要模擬幾組呼叫
@Before
fun setUp() {
// return the mock root from the mock inflater
doReturn(mMockConvertView).`when`(mMockInflater).inflate(R.layout.my_layout, mMockViewGroup, false)
// extra mocks to handle findChildViewById
doReturn(1).`when`(mMockConvertView).childCount
doReturn(mMockConvertView).`when`(mMockConvertView).getChildAt(0)
// Return the mocked views
doReturn(mMockText).`when`(mMockConvertView).findViewById<View>(R.id.text)
doReturn(mMockButton).`when`(mMockConvertView).findViewById<View>(R.id.get_text)
doReturn(mMockProgBar).`when`(mMockConvertView).findViewById<View>(R.id.progress)
}
他們最近將其更改為 useViewBindings.findChildViewById而不是 just findViewById,這需要額外的模擬。
@Nullable
public static <T extends View> T findChildViewById(View rootView, @IdRes int id) {
if (!(rootView instanceof ViewGroup)) {
return null;
}
final ViewGroup rootViewGroup = (ViewGroup) rootView;
final int childCount = rootViewGroup.getChildCount();
for (int i = 0; i < childCount; i ) {
final T view = rootViewGroup.getChildAt(i).findViewById(id);
if (view != null) {
return view;
}
}
return null;
}
請記住,它們將來可能會更改自動生成代碼的結構,這會破壞這樣的單元測驗。這是最近發生的,當他們切換到這種靜態方法時,如果將來再次發生,我不會感到驚訝。
定義了這些之后,您就可以呼叫
val binding = ActivityMainBinding.inflate(mMockInflater, mMockViewGroup, false)
獲得一個實際的系結實體來保存你的模擬視圖。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/385175.html
標籤:安卓 单元测试 模拟 android-viewbinding androidx-测试
