๐ก์ฐ์ํํ ํฌ์ฝ์ค ๊ณผ์ ์์ ํ์ตํ MockWebServer์ ๋ํ์ฌ ๊ธฐ๋กํ์์ต๋๋ค.
MockWebServer
- MockWebServer๋ ๋คํธ์ํฌ ํต์ ์ ํ ์คํธํ ๋ ๊ฐ์ง ์๋ฒ๋ฅผ ๋ง๋ค์ด HTTP ์์ฒญ์ ์ฒ๋ฆฌํ๊ณ , ๋ฏธ๋ฆฌ ์ ์๋ ์๋ต์ ๋ฐํํ๋ ํ ์คํธ ๋๊ตฌ์ ๋๋ค.
- ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ OkHttp์์ ์ ๊ณตํ๋ฉฐ Retrofit, OkHttp ๋ฑ์ ๋คํธ์ํฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํจ๊ป ๋จ์ ํ ์คํธ๋ฅผ ์์ฑํ ๋ ์ ์ฉํ๊ฒ ์ฌ์ฉ๋ฉ๋๋ค.
https://github.com/square/okhttp/tree/master/mockwebserver
์ฃผ์ ๊ธฐ๋ฅ
- ๊ฐ์ง ์๋ฒ ํ๊ฒฝ ์ ๊ณต
- ์ค์ ์๋ฒ ์์ด๋ ๋คํธ์ํฌ ์์ฒญ์ ๋ํด ๋ฏธ๋ฆฌ ์ ์๋ ์๋ต์ ๋ฐํํ๋ ์๋ฒ ํ๊ฒฝ์ ์ ๊ณตํฉ๋๋ค.
- ๋คํธ์ํฌ ์์ฒญ ๊ฒ์ฆ
- ํ ์คํธ ์ค์ ๋ฐ์ํ ์์ฒญ ๋ด์ฉ์ ํ์ธํ ์ ์์ต๋๋ค.
- ๋ค์ํ ์๋ต ์๋ฎฌ๋ ์ด์
- HTTP ์๋ต ์ฝ๋๋ฅผ ์ค์ ํ๊ฑฐ๋, ์๋ต ์ง์ฐ์ ์๋ฎฌ๋ ์ด์ ํ๋ ๋ฑ ๋ค์ํ ๋คํธ์ํฌ ์ํฉ์ ํ ์คํธํ ์ ์์ต๋๋ค.
Gradle
- MockWebServer๋ฅผ ํ๋ก์ ํธ์ ์ถ๊ฐํฉ๋๋ค.
dependencies {
testImplementation 'com.squareup.okhttp3:mockwebserver:4.9.3'
}
MockWebServer ๊ตฌํ
- ์ฐ์ํํ ํฌ์ฝ์ค ํ๋ ์ค์ MockWebServer๋ฅผ ํ์ฉํ Service๋ฅผ ๊ตฌํํ์์ต๋๋ค.
- ์ปดํฌ๋ํธ Service๊ฐ ์๋, CRUD๋ฅผ ์ ์ํ๊ธฐ ์ํ ์ธํฐํ์ด์ค๋ฅผ Service๋ก ๋ค์ด๋ฐ์ ์์ฑํ์์ต๋๋ค.
- ์ด๋ฅผ ํตํด Retrofit์ ์ธํฐํ์ด์ค ๊ธฐ๋ฐ ๋คํธ์ํฌ ์์ฒญ์ ํ ์คํธ ํ๊ฒฝ์ผ๋ก ๊ตฌ์ถํ ์ ์์ต๋๋ค.
interface ProductService {
fun findAll(): List<Product>
fun findProductById(productId: Long): Product?
fun findPagingProducts(
offset: Int,
pagingSize: Int,
): List<Product>
}
์๋ต ์ ์
- MockResponse๋ฅผ ํ์ฉํด์ ์๋ต ์ฝ๋์ ์๋ต ๋ฐ์ดํฐ๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.
- setResponseCode(200)์ผ๋ก HTTP ์ํ ์ฝ๋๋ฅผ, setBody()๋ก ์๋ต ๋ณธ๋ฌธ์ ์ค์ ํฉ๋๋ค.
private fun openServer(
server: MockWebServer,
body: String,
) {
server.enqueue(MockResponse().setBody(body).setResponseCode(200))
}
MockProductService
- MockProductService๋ ProductService๋ฅผ MockWebServer๋ก ๊ตฌํํ ๊ฐ์ ํ๊ฒฝ์ ๋๋ค.
- Request๋ฅผ ์์ฑํด์ Response๋ฅผ ๋ฐ๊ณ , Gson ํ์์ผ๋ก ๋ฐํํ๋ ์ฝ๋๋ฅผ ์์ฑํ์์ต๋๋ค.
- findPagingProducts()๋ ํ์ฌ ๋ฐ์ดํฐ ์์น์ ์ด ํ์ด์ง ์ฌ์ด์ฆ๋ฅผ ๋ฐ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋ ํจ์์ ๋๋ค.
- ProductDatabase์ ์๋ ๋ฐ์ดํฐ๋ฅผ Retofit ๊ธฐ๋ฐ์ผ๋ก ์์ฒญํ๋ ์์ ์ ํ ์คํธํ ์ ์์์ต๋๋ค.
class MockProductService : ProductService {
private val client = OkHttpClient()
override fun findPagingProducts(
offset: Int,
pagingSize: Int,
): List<Product> {
val pagingData =
if (offset >= ProductDatabase.products.size) {
emptyList()
} else {
ProductDatabase.products.drop(offset)
}.take(pagingSize)
val body = Gson().toJson(pagingData)
val serverRequest = makeServerRequest(body, "?offset=$offset&pagingSize=$pagingSize")
val response: Response = client.newCall(serverRequest).execute()
val responseBody = response.body?.string() ?: return emptyList()
val productType = object : TypeToken<List<Product>>() {}.type
return Gson().fromJson(responseBody, productType)
}
private fun makeServerRequest(
body: String,
requestUrl: String,
): Request {
val server = MockWebServer()
openServer(server, body)
return Request.Builder()
.url(makeServerUrl(server, requestUrl))
.build()
}
private fun openServer(
server: MockWebServer,
body: String,
) {
server.enqueue(MockResponse().setBody(body).setResponseCode(200))
}
private fun makeServerUrl(
server: MockWebServer,
requestUrl: String,
): String {
val serverUrl = server.url(MOCK_SERVER_PATH).toString()
return "${serverUrl}$requestUrl"
}
private fun makeResponse(serverRequest: Request): Response {
return client.newCall(serverRequest).execute()
}
companion object {
private const val DEFAULT_URL = ""
private const val MOCK_SERVER_PATH = "/products"
}
}
Retrofit ๊ธฐ๋ฐ ๋คํธ์ํฌ ์์ฒญ
- ์ค์ ๋ก๋ ํ ์คํธ ํ๊ฒฝ์์ ์ฌ์ฉ๋์ง๋ง, ์์ฒญ์ ๋ํ Retrofit ๊ธฐ๋ฐ ์๋ต์ ๋ฐ๊ธฐ ์ํด ๊ตฌํํ์์ต๋๋ค.
class ProductRepositoryImpl : ProductRepository {
private val mockProductService: ProductService = MockProductService()
override fun loadPagingProducts(offset: Int): List<Product> {
var pagingData: List<Product> = listOf()
thread {
pagingData = mockProductService.findPagingProducts(offset, PRODUCT_LOAD_PAGING_SIZE)
}.join()
if (pagingData.isEmpty()) throw NoSuchDataException()
return pagingData
}
}
์ ๋ฆฌ
- MockWebServer๋ ์๋๋ก์ด๋์์ ๋คํธ์ํฌ ํต์ ํ ์คํธ๋ฅผ ์ํ ๋งค์ฐ ์ ์ฉํ ๋๊ตฌ์ ๋๋ค.
- ์ค์ ์๋ฒ ์์ด๋ ๋คํธ์ํฌ ์์ฒญ์ ์ฒ๋ฆฌํ๊ณ , ๋ค์ํ ์๋ต ์๋๋ฆฌ์ค๋ฅผ ํ ์คํธํ ์ ์์ต๋๋ค.
- ์ด๋ฅผ ํตํด Retrofit, OkHttp์ ๊ฐ์ ๋คํธ์ํฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํ ์คํธ๋ฅผ ๋์ฑ ์ฝ๊ฒ ์ํํ ์ ์์ต๋๋ค.