Hilt kullanılan Android projesinde Retrofit testi

Öncelikle Hilt, testing dokümantasyonunun Introduction kısmında yazdığı üzere sadece Android Instrumentation ve Robolectric testlerini destekliyor. Bu yüzden RequestServiceAndroidTest.kt dosyamı Android Test kısmında oluşturdum.

Android Instrumentation test için dokümantasyon bizden AndroidJunitRunner sınıfını kalıtım alan yeni bir sınıf oluşturmamızı ve Gradle içinde class path'inin ayarlanmasını istiyor. Bu, Hilt'in tüm instrumentation testlerinde çalışmasını sağlıyor.

MyTestRunner.kt

class MyTestRunner: AndroidJUnitRunner() {
    override fun newApplication(
        cl: ClassLoader?,
        className: String?,
        context: Context?
    ): Application {
        return super.newApplication(cl, HiltTestApplication::class.java.name, context)
    }
}

 

Gradle(Module)

defaultConfig {
    testInstrumentationRunner "com.example.myexampleapp.MyTestRunner"
}


Retrofit Servis

RequestService.kt

interface RequestService {
    @GET(BASE_URL)
    suspend fun searchImageRequest(
        @Query("key") key: String = API_KEY,
        @Query("q") search: String,
        @Query("image_type") imageType: String = "all",
        @Query("orientation") orientation: String = "all",
        @Query("min_width") minWidth: Int = 0,
        @Query("min_height") minHeight: Int = 0,
        @Query("colors") colors: String? = null,
        @Query("editors_choice") editorsChoice: Boolean = false,
        @Query("safesearch") safeSearch: Boolean = true,
        @Query("order") order: String = "popular",
        @Query("page") page: Int = 1,
        @Query("per_page") perPage: Int = 20,
    ): Response<ImagesModel>
}


Test

Dokümantasyona bakmadan önce ben testi şu şekilde yapmıştım, doğru yöntem değil fakat göstermek istiyorum: 

RequestServiceAndroidTest.kt

@HiltAndroidTest
class RequestServiceAndroidTest {

    @get:Rule
    var hiltRule = HiltAndroidRule(this)

    private lateinit var requestService: RequestService
    private lateinit var response: Response<ImagesModel>

    @InstallIn(SingletonComponent::class)
    @EntryPoint
    interface RequestServiceAndroidTestEntryPoint{
        fun provideRequestService(): RequestService
    }

    @Before
    fun initRequestService(){
        val hiltEntryPoint = EntryPointAccessors.fromApplication(ApplicationProvider.getApplicationContext(), RequestServiceAndroidTestEntryPoint::class.java)
        requestService = hiltEntryPoint.provideRequestService()
    }

    @Before
    fun getRequestServiceResponse() = runTest {
        response = requestService.searchImageRequest(search="image")
    }

    @Test
    fun errorBodyIsNull(){
        assert(response.errorBody() == null)
    }

    @Test
    fun responseBodyIsNotNull(){
        assert(response.body() != null)
    }

    @Test
    fun httpCodeIs200(){
        assert(response.code() == 200)
    }

}

Her Hilt testinde, Hilt bileşenlerinin oluşturulabilmesi için @HiltAndroidTest annotation eklenmesi ve enjeksiyonun sağlanması için de HiltAndroidRule eklenmesi gerekiyor

Benim yaptığım testteki şu kısım (@EntryPoint annotation); Hilt'in Activity, Fragment gibi desteklediği sınıflar haricindeki sınıflara enjeksiyon yapmak için kullanılıyor:

@InstallIn(SingletonComponent::class)
@EntryPoint
interface RequestServiceAndroidTestEntryPoint{
    fun provideRequestService(): RequestService
}

@Before
fun initRequestService(){
    val hiltEntryPoint = EntryPointAccessors.fromApplication(ApplicationProvider.getApplicationContext(), RequestServiceAndroidTestEntryPoint::class.java)
    requestService = hiltEntryPoint.provideRequestService()
}

 

Fakat testlerde enjeksiyon için dokümantasyonun önerdiği çok daha kısa bir yöntem var:

RequestServiceAndroidTest.kt

@HiltAndroidTest
class RequestServiceAndroidTest {

    @get:Rule
    var hiltRule = HiltAndroidRule(this)

    @Inject
    lateinit var requestService: RequestService
    private lateinit var response: Response<ImagesModel>

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

    @Before
    fun getRequestServiceResponse() = runTest {
        response = requestService.searchImageRequest(search="image")
    }

    @Test
    fun errorBodyIsNull(){
        assert(response.errorBody() == null)
    }

    @Test
    fun responseBodyIsNotNull(){
        assert(response.body() != null)
    }

    @Test
    fun httpCodeIs200(){
        assert(response.code() == 200)
    }

}

assertNull(), assertEquals() gibi diğer Assert metotlarını (eğer görünmüyorlarsa) import org.junit.Assert.* yazarak import edebilirsiniz.

Büyütmek için tıklayın


Şöyle bir durum var. @Before metotlarının hepsi her @Test metodundan önce tekrar çalıştırılır. Çünkü JUnit her test metodundan önce test sınıfının yeni bir objesini oluşturuyor. Bu yüzden testimizde her @Test metodu için tekrar bir enjeksiyon ve http isteği gerçekleştiriliyor. Bunun sorunun normalde iki çözümü var fakat Hilt kullanıldığı için ikisinin de bize faydası yok. Yine de not olarak yazıyorum:

@Before yerine @BeforeClass annotation kullanmak

@BeforeClass annotation, ilgili metodu tüm testlerden önce sadece bir defa çalıştırıyor. Fakat referansında yazdığı üzere metot static olmak zorunda. Bizim durumumuzda bunu Kotlinle yapmayı denersek fonksiyonumuzu Java'daki gibi static yapmak için @JVMStatic annotation kullanmamız gerekir. Fakat bu annotation, bir Class içinde ancak companion object kapsamı içerisinde kullanılabiliyor. Hilt ise companion object içerisinde enjeksiyon gerçekleştiremiyor.

JUnit5 @TestInstance annotation

JUnit5 versiyonunda @TestInstance annotation bulunuyor. Bu annotation, sınıftaki tüm test metotlarının tek bir test sınıfı objesi içinde çalışmasını sağlıyor. Fakat Android instrumented testlerde şuan JUnit5 araştırdığım kadarıyla kullanılamıyor(yanlış ise yorumda yazınız). Hilt de unit testte kullanılamıyor. Fakat bu yöntemi Android instrumented testte kullanabiliyor olsaydık şöyle yapabilirdik. Sınıfımızı @TestInstance(Lifecycle.PER_CLASS) şeklinde annotate ederdik. lateinit var şeklinde tanımlı response değişkenimizi ilk testin başında initialize(ilkleme) ederdik. Böylece ardındaki testlerde tekrar tekrar http isteği gerçekleşmezdi. Fakat bu sefer de şöyle bir durum var. @Test metotlarının çalıştırılma sırası sınıf içinde yazıldıkları sırada gerçekleştirilmeyebiliyor. Bu durumu da @OrderWith veya @FixMethodOrder annotation ile çözerdik.


Kaynak

https://developer.android.com/training/dependency-injection/hilt-testing

https://developer.android.com/training/testing/instrumented-tests

https://dagger.dev/hilt/testing.html

https://dagger.dev/hilt/instrumentation-testing

https://developer.android.com/codelabs/android-hilt#10

https://junit.org/junit5/docs/current/user-guide/

https://junit.org/junit5/docs/5.0.1/api/

https://github.com/junit-team/junit4/wiki/

https://rommansabbir.com/android-network-calling-with-retrofit-and-unit-testing

Yorumlar