# Table of Contents

# MockWebServer로 Retrofit2 단위 테스트

MockWebServer 라이브러리를 사용하면 API 서버 목업을 만들어 쉽게 테스트할 수 있다.

# 의존성 추가

// 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'

# 사용법

Retrofit2를 사용한 AuthApi인터페이스를 테스트하려고 한다.

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

테스트 코드는 다음과 같이 작성할 수 있다.

import com.google.common.truth.Truth.assertThat
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.yologger.app.api.auth.model.LoginRequest
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Test
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.net.HttpURLConnection

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 `로그인 성공 테스트`() {
        // Given
        val dummyUserId = 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")
        val response = authApi.login(request).execute()

        // Then
        assertThat(response.isSuccessful).isTrue()
        assertThat(response.body()?.userId).isEqualTo(dummyUserId)
        assertThat(response.body()?.accessToken).isEqualTo(dummyAccessToken)
        assertThat(response.body()?.refreshToken).isEqualTo(dummyRefreshToken)
    }
}

# enqueue()

위 예제에서 MockWebServer클래스의 enqueue()를 사용하여 MockResponse를 추가했다.

mockServer.enqueue(successResponse)

enqueue()는 내부적으로 Queue를 사용하기 때문에 FIFO구조로 응답하게 된다.

// 첫 번째 가상 응답 추가
val response1 = MockResponse()
    .setResponseCode(HttpURLConnection.HTTP_OK)
    .setBody("{\"message\":\"response1\"}")
mockServer.enqueue(response1)

// 두 번째 가상 응답 추가
val response2 = MockResponse()
    .setResponseCode(HttpURLConnection.HTTP_OK)
    .setBody("{\"message\":\"response1\"}")
mockServer.enqueue(response2)

// 첫 번째 요청 후 결과 확인
val request1 = EmailVerificationCodeRequest("ronaldo@gmail.com")
val result1 = authService.emailVerificationCode(request1).execute()
assertThat(result1.body()!!.message).isEqualTo("response1")

// 두 번째 요청 후 결과 확인
val request2 = EmailVerificationCodeRequest("ronaldo@gmail.com")
val result2 = authService.emailVerificationCode(request2).execute()
assertThat(result2.body()!!.message).isEqualTo("response2")

# Dispatcher

Dispatcher를 사용하면 요청 URL에 따라 다른 가짜 응답을 하도록 구현할 수 있다.

import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest

val dispatcher = object: Dispatcher() {
    override fun dispatch(request: RecordedRequest): MockResponse {
        return when(request.path) {
            "/auth/login" -> MockResponse().apply {
                setResponseCode(200)
                setBody("{\"message\":\"logged in.\"}")
            }
            "/auth/join" -> MockResponse().apply {
                setResponseCode(200)
                setBody("{\"data\":\"joined.\"}")
            }
            else -> MockResponse().apply {
                setResponseCode(404)
                setBody("{\"error\":\"Not Found\"}")
            }
        }
    }
}

server.dispatcher = dispatcher