# 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`