エミュレータに接続して、OkHttpの実装リポジトリOkHttpHtmlRepositoryImplのテストを行います。
実際に外部サイト(テスト用サイト)に接続してのテストになります。
エミュレータテストに必要なライブラリを:core:dataモジュールのbuild.gradle.ktsに追記します。
:
android {
:
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
:
// エミュレータ接続テスト (src/androidTest)
androidTestImplementation(libs.bundles.androidx.test)
androidTestImplementation(platform(libs.junit5.bom))
androidTestImplementation(libs.bundles.junit5)
androidTestRuntimeOnly(libs.junit5.engine)
androidTestImplementation(libs.kotlinx.coroutines.test)
}
追記後、『同期アイコン』で内容をプロジェクトに反映させます。
AndroidManifestで外部との通信(インターネット接続)を許可します。
: <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> </manifest>
JUnit 5スタイルで実機/エミュレータテストを行うための拡張(Extension)クラスをandroidTestに作成します。
package jp.co.example.android01.core.data
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.jupiter.api.extension.*
class AndroidJUnit5Extension : BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback {
override fun beforeAll(context: ExtensionContext) {
val instrumentation = InstrumentationRegistry.getInstrumentation()
Log.i("AndroidJUnit5Extension", "Instrumentation initialized: $instrumentation")
}
override fun beforeEach(context: ExtensionContext) {
Log.i("AndroidJUnit5Extension", "Starting test: ${context.displayName}")
}
override fun afterEach(context: ExtensionContext) {
Log.i("AndroidJUnit5Extension", "Finished test: ${context.displayName}")
}
override fun afterAll(context: ExtensionContext) {
Log.i("AndroidJUnit5Extension", "All tests completed for: ${context.testClass.map { it.simpleName }.orElse("UnknownClass")}")
}
}
:core:dataモジュールのandroidTestにエミュレータテスト用クラスを作成します。
ファイル名をOkHttpHtmlRepositoryImplAndroidTestにしています。
package jp.co.example.android01.core.data
import android.util.Log
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.extension.ExtendWith
import jp.co.example.android01.core.data.repository.OkHttpHtmlRepositoryImpl
/**
* OkHttpHtmlRepositoryImplのAndroid Instrumented Test
* 実際のOkHttpクライアントを使用してテストを実行します
*/
@ExtendWith(AndroidJUnit5Extension::class)
@DisplayName("OkHttpHtmlRepositoryImpl Android Tests")
class OkHttpHtmlRepositoryImplAndroidTest {
private lateinit var context: Context
private lateinit var repository: OkHttpHtmlRepositoryImpl
@BeforeEach
fun setUp() {
// テスト用のContextを取得
context = InstrumentationRegistry.getInstrumentation().targetContext
repository = OkHttpHtmlRepositoryImpl()
Log.i("OkHttpAndroidTest", "OkHttpHtmlRepositoryImpl initialized")
}
@AfterEach
fun tearDown() {
// クリーンアップ
Log.i("OkHttpAndroidTest", "Test cleanup completed")
}
@Nested
@DisplayName("Get HTML Tests")
inner class GetHtmlTests {
@Test
@DisplayName("実際のWebサイトからHTMLコンテンツを取得 - 成功")
fun getHtmlContent_Success() = runTest {
// Given
val testUrl = "https://httpbin.org/html" // テスト用のHTMLを返すサイト
// When
val result = repository.getHtmlContent(testUrl)
// Then
logLong("=== OkHttp Real Website Test Result ===")
if (result.isSuccess) {
val content = result.getOrNull() ?: "(no content)"
logLong("Content length: ${content.length}")
logLong("Content preview: $content")
} else {
Log.e("OkHttpAndroidTest", "Fetch failed", result.exceptionOrNull())
}
logLong("=======================================")
assertTrue(result.isSuccess, "結果は成功であるべき")
assertNotNull(result.getOrNull(), "HTMLコンテンツはnullでない")
assertTrue(
result.getOrNull()?.contains("html") == true,
"HTMLコンテンツが含まれている"
)
assertTrue(result.getOrNull()?.isNotEmpty() == true, "コンテンツが空でない")
}
}
@Nested
@DisplayName("Error Handling Tests")
inner class ErrorHandlingTests {
@Test
@DisplayName("404エラー時 失敗を返す - エラーハンドリング")
fun getHtmlContent_404Error() = runTest {
// Given
val testUrl = "https://httpbin.org/status/404"
// When
val result = repository.getHtmlContent(testUrl)
// Then
logLong("=== OkHttp 404 Error Test Result ===")
logLong("Success: ${result.isSuccess}")
logLong("Failure: ${result.isFailure}")
logLong("Exception: ${result.exceptionOrNull()?.message}")
logLong("===================================")
assertTrue(result.isFailure, "結果は失敗であるべき")
assertNotNull(result.exceptionOrNull(), "エラーメッセージが存在する")
assertTrue(
result.exceptionOrNull()?.message?.contains("HTTP Error: 404") == true,
"HTTPエラーメッセージが含まれている"
)
}
@Test
@DisplayName("500エラー時 失敗を返す - エラーハンドリング")
fun getHtmlContent_500Error() = runTest {
// Given
val testUrl = "https://httpbin.org/status/500"
// When
val result = repository.getHtmlContent(testUrl)
// Then
logLong("=== OkHttp 500 Error Test Result ===")
logLong("Success: ${result.isSuccess}")
logLong("Failure: ${result.isFailure}")
logLong("Exception: ${result.exceptionOrNull()?.message}")
logLong("===================================")
assertTrue(result.isFailure, "結果は失敗であるべき")
assertNotNull(result.exceptionOrNull(), "エラーメッセージが存在する")
assertTrue(
result.exceptionOrNull()?.message?.contains("HTTP Error: 500") == true,
"HTTPエラーメッセージが含まれている"
)
}
@Test
@DisplayName("存在しないURL - エラーハンドリング")
fun getHtmlContent_NonExistentUrl() = runTest {
// Given
val testUrl = "https://this-domain-does-not-exist-12345.com"
// When
val result = repository.getHtmlContent(testUrl)
// Then
logLong("=== OkHttp Non-existent URL Test Result ===")
if (result.isFailure) {
val exception = result.exceptionOrNull()
logLong("Expected failure: ${exception?.message}")
} else {
logLong("Unexpected success: ${result.getOrNull()?.take(100)}")
}
logLong("===========================================")
// 存在しないURLでもOkHttpは何らかのHTMLを返す可能性がある
// 成功または失敗のいずれかが適切に処理されることを確認
assertNotNull(result.getOrNull() ?: result.exceptionOrNull(), "結果は適切に処理されるべき")
}
// 無効なURL時_失敗を返す
@Test
@DisplayName("無効なURL - エラーハンドリング")
fun getHtmlContent_InvalidUrl() = runTest {
// Given
val invalidUrl = "invalid-url"
// When
val result = repository.getHtmlContent(invalidUrl)
// Then
logLong("=== OkHttp Invalid URL Test Result ===")
logLong("Success: ${result.isSuccess}")
logLong("Failure: ${result.isFailure}")
logLong("Exception: ${result.exceptionOrNull()?.message}")
logLong("=====================================")
assertTrue(result.isFailure, "結果は失敗であるべき")
assertNotNull(result.exceptionOrNull(), "エラーメッセージが存在する")
}
}
@Nested
@DisplayName("Edge Case Tests")
inner class EdgeCaseTests {
@Test
@DisplayName("リダイレクトURL - リダイレクトテスト")
fun getHtmlContent_RedirectUrl() = runTest {
// Given
val testUrl = "https://httpbin.org/redirect/1" // 1回リダイレクト
// When
val result = repository.getHtmlContent(testUrl)
// Then
logLong("=== OkHttp Redirect Test ===")
if (result.isSuccess) {
val content = result.getOrNull() ?: "(no content)"
logLong("Redirect handling successful")
logLong("Content length: ${content.length}")
} else {
Log.e("OkHttpAndroidTest", "Redirect fetch failed", result.exceptionOrNull())
}
logLong("=============================")
// リダイレクトは成功するか、適切にエラーハンドリングされる
assertNotNull(result.getOrNull() ?: result.exceptionOrNull(), "リダイレクトは適切に処理されるべき")
}
}
// 4000文字制限対応の分割ログ
private fun logLong(message: String) {
if (message.length <= 4000) {
Log.d("OkHttpAndroidTest", message)
} else {
var start = 0
val length = message.length
while (start < length) {
val end = (start + 4000).coerceAtMost(length)
Log.d("OkHttpAndroidTest", message.substring(start, end))
start = end
}
}
}
}
エミュレータテストの実行は、Android Studioのターミナルで行います。
エミュレータテストするクラスファイルを右クリック➡実行(U)でも実行できます。
※エミュレータテストの実行前にエミュレーターを起動しておいてください。
# 端末にテスト用のモジュールをインストールします ./gradlew :core:data:installDebugAndroidTest # すべてのエミュレータテスト & "$env:ANDROID_HOME\platform-tools\adb.exe" shell am instrument -w ` -e runnerBuilder de.mannodermaus.junit5.AndroidJUnit5Builder ` jp.co.example.android01.core.data.test/androidx.test.runner.AndroidJUnitRunner # OkHttpのみのエミュレータテスト & "$env:ANDROID_HOME\platform-tools\adb.exe" shell am instrument -w ` -e runnerBuilder de.mannodermaus.junit5.AndroidJUnit5Builder ` -e class jp.co.example.android01.core.data.OkHttpHtmlRepositoryImplAndroidTest ` jp.co.example.android01.core.data.test/androidx.test.runner.AndroidJUnitRunner # OkHttp内のgetHtmlContent_Successのみのエミュレータテスト & "$env:ANDROID_HOME\platform-tools\adb.exe" shell am instrument -w ` -e runnerBuilder de.mannodermaus.junit5.AndroidJUnit5Builder ` -e class jp.co.example.android01.core.data.OkHttpHtmlRepositoryImplAndroidTest#getHtmlContent_Success ` jp.co.example.android01.core.data.test/androidx.test.runner.AndroidJUnitRunner
実行結果は、Logcat内に出力されています。