# Table of Contents

# Retrofit과 RxJava 함께 사용하기

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

# 의존성 추가

Retrofit 응답을 Observable로 변환하기 위해서는 adapter-rxjava3을 추가해야 한다.









 








dependencies {
    // RxJava
    implementation "io.reactivex.rxjava3:rxkotlin:3.0.0"
    implementation "io.reactivex.rxjava3:rxandroid:3.0.0"

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

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

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

# 사용하기

RetrofitCall 대신 RxJavaObservable 타입으로 반환이 가능하다.

import io.reactivex.rxjava3.core.Observable

interface AuthApi {
    @POST("/auth/login")
    fun login(@Body request: LoginRequest): Observable<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
)

RxJava3CallAdapterFactory.create()를 다음과 같이 추가한다.












 



import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory

private lateinit var authApi: AuthApi

val client = OkHttpClient.Builder()
    .build()

authApi = Retrofit.Builder()
    .client(client)
    .baseUrl(BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
    .build()
    .create(AuthApi::class.java)

이제 다음과 같이 응답을 Observable로 반환받을 수 있다.

val request = LoginRequest("paul@gmail.com", "1234")
authApi.login(request)
    .subscribe({ response ->
        // 성공
    }, { error ->
        // 실패
    }, {
        
    }).addTo(disposables)

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

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

authApi.login(request)
    .subscribe({ response ->
        // 2xx 성공
    }, { error ->
        when (error) {
            is HttpException -> {
                // 4xx, 5xx Error
                val errorCode: Int = error.code()
                val errorBody: ResponseBody? = error.response()?.errorBody()
            }
            is ConnectException -> {
                // Connection Error
                val message = error.message
            }
            else -> {
                // Other Errors
            }
        }
    }, {

    }).addTo(disposables)

# MockWebServer로 단위 테스트 하기

Observable클래스의 blockingSubscribe()메소드를 사용하면 동기적으로 네트워크 통신 후 응답을 수신할 수 있다.

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())
            .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
            .build()
            .create(AuthApi::class.java)
    }

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

    @Test
    fun `로그인 성공 테스트`() {
        // 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
        authApi.login(request)
            .blockingSubscribe({response ->
                assertThat(response.userId).isEqualTo(dummyUserId)
                assertThat(response.accessToken).isEqualTo(dummyAccessToken)
                assertThat(response.refreshToken).isEqualTo(dummyRefreshToken)
            }, {error ->
                fail()
            }, {

            })
    }

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

        // 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
        authApi.login(request)
            .blockingSubscribe({response ->
                fail()
            }, { error ->
                if (error is HttpException) {
                    assertThat(error.code()).isEqualTo(HttpURLConnection.HTTP_BAD_REQUEST)
                } else {
                    fail()
                }
            })
    }
}