在测试类中膨胀 ViewBinding 时出错:Binary XML file line #38: Binary XML file line #38: Error inflating class &l
Posted
技术标签:
【中文标题】在测试类中膨胀 ViewBinding 时出错:Binary XML file line #38: Binary XML file line #38: Error inflating class <unknown>【英文标题】:Error inflating ViewBinding in test class : Binary XML file line #38: Binary XML file line #38: Error inflating class <unknown> 【发布时间】:2022-01-18 15:57:10 【问题描述】:我正在尝试为使用 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: TypedValuet=0x2/d=0x7f04015d a=2
我在测试类中找不到 ViewBinding inflate 的代码示例,这可能吗? I found this *** thread 但它使用 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...
【问题讨论】:
【参考方案1】:通过查看生成的绑定类以查看我需要哪些方法,我能够让这个工作(使用 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)
他们最近将其更改为使用 ViewBindings.findChildViewById
而不仅仅是 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)
获取一个实际的绑定实例来保存您的模拟视图。
【讨论】:
太好了,终于成功了!感谢您的帮助,我确实阅读了生成的绑定类,但我没有像您那样挖掘足够的东西来找到正确的模拟。我将编辑我的原始帖子以使用 mockK 编写解决方案以上是关于在测试类中膨胀 ViewBinding 时出错:Binary XML file line #38: Binary XML file line #38: Error inflating class &l的主要内容,如果未能解决你的问题,请参考以下文章
第 7 行的 InflateException 二进制 XML 文件:膨胀类片段时出错
如何解决:二进制 XML 文件第 8 行:膨胀类时出错 [重复]
二进制 XML 文件第 0 行:使用 api 26 膨胀类 TextView 时出错