今天來寫一些 API 相關的測試,不過因為這個專案沒有用到網路相關的東西,後面的內容會以 github api 展示。
測試 API 的方式很多,而我自己比較喜歡的做法是利用 MockWebServer 協助我完成測試。
MockWebServer 是 Square 公司為 OkHttp 開發的 Mock Library,其原理是創建一個 Local 端的 web server, 使用時再把透過替換 domain 的方式讓這個假的 web server handle 住網路請求,並提供一些如 request 、 response 、 header 等相關的 API 讓使用者可以拿來做驗證。
先把 Retrofit 及 MockWebServer 的 library 加進來:
dependency {
def retrofitVersion = '2.6.1'
def mockwebserverVersion = '3.8.1'
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
implementation "com.squareup.okhttp3:mockwebserver:$mockwebserverVersion"
}
寫一個 API 獲得 coil 這個 repository 的資料:
interface SearchService {
@GET("search/repositories")
suspend fun searchRepo(
@Query("q", encoded = true) repo: String = "coil+org:coil-kt"
): SearchRepo
}
@Module
class NetworkModule {
@Singleton
@Provides
fun provideGithubService(): SearchService {
return Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(SearchService::class.java)
}
}
順便一提 Retrofit 在 2.6.0 之後已經可以支援 coroutines 的 suspend function 了。
我們需要為 MockWebServer 提供假資料,讓他可以 Mock 假的 Http Response ,這裏做法隨意,我是在 test 資料夾下建立一個 Java 的 resource 資料夾,把 SearchService 的回應 Json 檔放進去,之後再用 Java 的 IO API 把 Json 檔的資料讀出來,傳給 MockWebServer
在專案內放入假的 API Json 資料:
在 Test 資料夾建立測試,我們不會用到 Android 相關的 API ,並在 Before 階段初始化 MockWebServer :
@ExperimentalCoroutinesApi
class SearchServiceTest {
private lateinit var service: SearchService
private lateinit var mockWebServer: MockWebServer
@Before
fun setup() {
mockWebServer = MockWebServer()
service = Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(SearchService::class.java)
}
@After
fun dropdown() {
mockWebServer.shutdown()
}
}
接著把 Retrofit domain 替換成 MockWebServer ,值得注意的是 MockWebServer 要在 After 階段 shutdown 。
最後完成一個測試:
@ExperimentalCoroutinesApi
class SearchServiceTest {
......
@Test
fun searchRepoThenResponse() {
val query = "coil+org:coil-kt"
enqueueResponse("search-repo.json")
val response = runBlocking {
service.searchRepo(query)
}
val request = mockWebServer.takeRequest()
assertThat(request.path, `is`("/search/repositories?q=$query"))
val items = response.items
assertThat(items.size, `is`(1))
assertThat(items[0].name, `is`("coil"))
assertThat(items[0].fullName, `is`("coil-kt/coil"))
assertThat(items[0].gitUrl, `is`("git://github.com/coil-kt/coil.git"))
assertThat(items[0].owner.login, `is`("coil-kt"))
}
private fun enqueueResponse(fileName: String, headers: Map<String, String> = emptyMap()) {
val classloader = javaClass.classLoader
val inputStream = classloader.getResourceAsStream("api-response/$fileName")
val source = Okio.buffer(Okio.source(inputStream))
val mockResponse = MockResponse()
for ((key, value) in headers) {
mockResponse.addHeader(key, value)
}
mockWebServer.enqueue(
mockResponse
.setBody(source.readString(Charsets.UTF_8))
)
}
}
我寫了一個 enqueueResponse()
方法來讀取 Json 檔的資料,並把它設置成 Response 的 body ,之後再調用 API 時就可以接收到 API 資料。
另外還可以使用他自己的 API 來測試 API path 是否與自己所設定的一致,這樣就完成一個 API 測試了。
API Test 的介紹到這邊,因為剩下的內容是把程式碼完善,明天開始會講一些其他的東西。