I am trying to write unit tests for a RecyclerView.ViewHolder class which uses ViewBinding but I am facing issues to inflate my ViewBinding in my test class, having this error when running my test :
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}
I could not find code examples of ViewBinding inflate in test classes, is that possible ? I found this StackOverflow thread but it uses PowerMock to mock a ViewBinding class. I'm using mockK in my project and I think using a real ViewBinding instance would be better in my case.
My ViewHolder looks like this :
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
}
}
}
My test class looks like this. I am using MockK and Robolectric to get application context
@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)
}
}
EDIT: This is the mockK version of the answer from @tyler-v
@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...
}
I was able to get this working (using Mockito, but it should be applicable to MockK too) by looking in the generated binding class to see what methods I needed to mock to get it to inflate and return mocked views properly. These files are in
app/build/generated/data_binding_base_class_source_out/debug/out/your/package/databindingfor a standard buildHere is an example of a generated data binding class with three views in a ConstraintLayout.
To be able to call inflate and have the binding holding mocked views in the unit test, you need to mock several sets of calls
They recently changed it to use
ViewBindings.findChildViewByIdinstead of justfindViewById, which required extra mocking.Keep in mind that they may change the structure of the auto-generated code in the future, which will break unit tests like this. This happened recently, when they switched to this static method, and it would not surprise me if it happened again in the future.
With these defined, then you could call
to get an actual binding instance holding your mocked views.