---
# System prepended metadata

title: Unit Test - 使用hilt測試8 -  Testing Image Picking
tags: [Unit Test, hilt, test, kotlin]

---

# Unit Test - 使用hilt測試8 -  Testing Image Picking


---


## 開始吧

### 情境
* 在這裡要驗證當選取了圖片之後是否有正確將url設定至變數
* 圖片是顯示在recycler view，用到Espresso對recyclerView操作

### 建立adapter吧

```kotlin=
class ImageAdapter @Inject constructor(
    var glide: RequestManager
) : RecyclerView.Adapter<ImageAdapter.ImageViewHolder>() {
    class ImageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)


    //效能取捨使用 diffUtil，址更新有變動的項目
    private val diffCallBack = object : DiffUtil.ItemCallback<String>() {
        override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
            return oldItem == newItem
        }

        override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
            return oldItem == newItem
        }
    }

    private val differ = AsyncListDiffer(this, diffCallBack)

    var images: List<String>
        get() = differ.currentList
        set(value) = differ.submitList(value)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
        return ImageViewHolder(
            LayoutInflater.from(parent.context).inflate(
                R.layout.item_image,
                parent,
                false
            )
        )
    }

    private var onItemClickListener: ((String) -> Unit)? = null

    fun setOnItemClcinListener(listener: (String) -> Unit) {
        onItemClickListener = listener
    }

    override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
        val url = images[position]
        holder.itemView.apply {
            glide.load(url).into(ivShoppingImage)

            setOnClickListener {
                onItemClickListener?.let { click ->
                    click(url)
                }
            }
        }
    }

    override fun getItemCount(): Int {
        return images.size
    }
}
```
### 建立FragmentFactory
* 在ImagePickFragment注入adapter
```kotlin=
class ImagePickFragment @Inject constructor(
    private val imageAdapter: ImageAdapter
): Fragment(R.layout.fragment_image_pick) {


}
```
* override Fragment Factory
    * 當實例化的class是ImagePickFragment，要傳入adapter這參數，若是其他類別就是預設的super方法

```kotlin=
class ShoppingFragmentFactory @Inject constructor(
    private val imageAdapter: ImageAdapter
) : FragmentFactory() {

    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        return when (className) {
            ImagePickFragment::class.java.name -> ImagePickFragment(imageAdapter)
            else -> super.instantiate(classLoader, className)
        }
    }
}
```

### 初始化recycler View & click listener

```kotlin=
class ImagePickFragment @Inject constructor(
    private val imageAdapter: ImageAdapter
) : Fragment(R.layout.fragment_image_pick) {

    lateinit var viewModel: ShoppingViewModel

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = ViewModelProvider(requireActivity()).get(ShoppingViewModel::class.java)
        setupRecyclerView()
        
        imageAdapter.setOnItemClcinListener {
            //當選取圖片之後，就會退回上一頁
            findNavController().popBackStack()
            viewModel.setCurImageUrl(it)
        }
    }

    private fun setupRecyclerView() {
        rvImages.apply {
            adapter = imageAdapter
            layoutManager = GridLayoutManager(requireContext(), GRID_SPAN_COUNT)
        }
    }
}
```

### 開始寫測試吧
* gradle新增espresso支援recyclerView操作
```kotlin=
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0'
```
* 如果測試到的元件有動畫效果，要關閉，他會干擾espresso
    * 一個方法是用adb指令，在terminal分三次輸入
        1. adb shell settings put global window_animation_scale 0.0
        2. adb shell settings put global transition_animation_scale 0.0
        3. adb shell settings put global animator_duration_scale 0.0
    * 另一個方法是設定gradle
```kotlin=
//gradle 
android {
    testOptions {
            animationsDisabled = true
        }
}
```
* 測試主體
```kotlin=
@HiltAndroidTest
@MediumTest
@ExperimentalCoroutinesApi
class ImagePickFragmentTest{

    //有使用到背景執行緒就要加入這個rule
    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()

    @get:Rule
    var hiltRule = HiltAndroidRule(this)

    //注入我們定義的factory，侷限在image pick fragment才注入adapter
    @Inject
    lateinit var fragmentFactory: ShoppingFragmentFactory

    @Before
    fun setup(){
        hiltRule.inject()
    }

    @Test
    fun clickImage_popBackStackAndSetImageUrl(){
        val navController = mock(NavController::class.java)
        val imageUrl = "test Url"
        val testViewModel = ShoppingViewModel(FakeRepository())

        launchFragmentInHiltContainer<ImagePickFragment>(fragmentFactory = fragmentFactory) {
            Navigation.setViewNavController(requireView(),navController)
            //傳遞資料給adapter
            imageAdapter.images = listOf(imageUrl)
            viewModel = testViewModel
        }

        onView(withId(R.id.rvImages)).perform(
            RecyclerViewActions.actionOnItemAtPosition<ImageAdapter.ImageViewHolder>(
                0,
                click()
            )
        )
        
        verify(navController).popBackStack()
      //驗證cuurImageUrl是否跟我們設定給他的一樣  
       assertThat(testViewModel.curImageUrl.getOrAwaitValue()).isEqualTo(imageUrl)

    }

}

```

![](https://i.imgur.com/uYDdu8r.png)

參考資料
[Philipp Lackner's channel](https://www.youtube.com/watch?v=IcceHTRi7vg&t=36s)

###### tags: `test` `Unit Test` `kotlin` `hilt`