# Table of Contents

# Retrofit과 RxJava 함께 사용하기

RetrofitRxJava를 함께 사용하는 방법에 대해 정리한다.

# 의존성 추가

RetrofitCoroutine과도 함께 사용할 수 있다.









 






dependencies {
    // Coroutine
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'

    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.8.2'
    implementation 'com.squareup.retrofit2:converter-gson:2.8.2'

    // MockWebServer
    testImplementation "com.squareup.okhttp3:mockwebserver:4.10.0"

    // Truth
    testImplementation 'com.google.truth:truth:1.1.3'
}

# 사용하기

이제 Retrofit 인터페이스의 메소드를 suspend로 선언할 수 있다. RetrofitCall 대신 RxJavaObservable 타입으로 반환이 가능하다.

interface AuthApi {
    @POST("/auth/login")
    suspend fun login(@Body request: LoginRequest): LoginResponse
}

모델은 다음과 같다.

import com.google.gson.annotations.SerializedName

data class LoginRequest (
    @SerializedName("email") val email: String,
    @SerializedName("password") val password: String
)
data class LoginResponse (
    @SerializedName("user_id") val userId: Long,
    @SerializedName("access_token") val accessToken: String,
    @SerializedName("refresh_token") val refreshToken: String
)

이제 코루틴 스코프 안에서 이 함수를 호출할 수 있다.

GlobalScope.launch {
    val response: LoginResponse = authApi.login(request)
}

try-catch 구문으로 에러 처리를 할 수 있다.

val request = LoginRequest("ronalo@gmail.com", "1234")

GlobalScope.launch {
    try {
        val response: LoginResponse = authApi.login(request)
    } catch (exception: Exception) {
        // 에러 처리
    }
}

error의 타입 비교를 통해 에러의 종류를 파악할 수 있다.

val request = LoginRequest("ronalo@gmail.com", "1234")

GlobalScope.launch {
    try {
        val response: LoginResponse = authApi.login(request)
    } catch (exception: Exception) {
        when(exception) {
            is HttpException -> {
                // 400, 500 Error
                val code: Int = exception.code()
                val errorBody: ResponseBody? = exception.response()?.errorBody()
            }
            is ConnectException -> {
                // Connection Error
            }
            else -> {
                // Other Exceptions
            }
        }
    }
}

# MockWebServer로 단위 테스트 하기

코루틴을 테스트하려면 다음 의존성을 추가해야한다.

dependencies {
    testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9"
}

runBlocking() 메소드를 사용하면 동기적으로 suspend 함수를 처리한 후 결과값을 테스트할 수 있다.

import kotlinx.coroutines.runBlocking
// ...

class AuthApiTest {
    lateinit var mockServer: MockWebServer
    lateinit var mockUrl: HttpUrl
    lateinit var client: OkHttpClient
    lateinit var authApi: AuthApi

    @Before
    fun setUp() {
        // Set up MockWebServer
        mockServer = MockWebServer()
        mockServer.start()

        // Set up Mock URL
        mockUrl = mockServer.url("/")

        // Set up Okhttp3 client
        client = OkHttpClient.Builder()
            .build()

        // Set up AuthApi
        authApi = Retrofit.Builder()
            .client(client)
            .baseUrl(mockUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(AuthApi::class.java)
    }

    @After
    fun tearDown() {
        // Shut down MockWebServer
        mockServer.shutdown()
    }

    @Test
    fun `로그인 성공 테스트`() = runBlocking {
        // Given
        val dummyUserId: Long = 1
        val dummyAccessToken = "qwekjqwlkejlkqwe"
        val dummyRefreshToken = "123jl12j3kj123lk"

        // Create mock response
        val successResponse by lazy {
            MockResponse().apply {

                val jsonObject = JsonObject()
                jsonObject.addProperty("user_id", dummyUserId)
                jsonObject.addProperty("access_token", dummyAccessToken)
                jsonObject.addProperty("refresh_token", dummyRefreshToken)

                val gson = Gson()

                val jsonString = gson.toJson(jsonObject)

                addHeader("Content-Type", "application/json")
                setResponseCode(HttpURLConnection.HTTP_OK)
                setBody(jsonString)
            }
        }

        // Add response to mock server
        mockServer.enqueue(successResponse)

        // When
        val request = LoginRequest("paul@gmail.com", "1234")

        // Then
        val response = authApi.login(request)
        assertThat(response.userId).isEqualTo(dummyUserId)
        assertThat(response.accessToken).isEqualTo(dummyAccessToken)
        assertThat(response.refreshToken).isEqualTo(dummyRefreshToken)
    }

    @Test
    fun `로그인 실패 테스트`() = runBlocking {
        // Given
        val dummyMessage = "fail"

        // Create mock response
        val successResponse by lazy {
            MockResponse().apply {

                val jsonObject = JsonObject()
                jsonObject.addProperty("message", dummyMessage)

                val gson = Gson()

                val jsonString = gson.toJson(jsonObject)

                addHeader("Content-Type", "application/json")
                setResponseCode(HttpURLConnection.HTTP_BAD_REQUEST)
                setBody(jsonString)
            }
        }

        // Add response to mock server
        mockServer.enqueue(successResponse)

        // When
        val request = LoginRequest("paul@gmail.com", "1234")

        // Then
        try {
            val response = authApi.login(request)
        } catch (e: HttpException) {
            assertThat(e.code()).isEqualTo(HttpURLConnection.HTTP_BAD_REQUEST)
        } catch (e: Exception) {
            fail()
        }
    }
}