エミュレータに接続して、WebViewの実装リポジトリWebViewHtmlRepositoryImplのテストを行います。
実際に外部サイト(テスト用サイト)に接続してのテストになります。
※単にサイトからHTMLを取得するだけですので、単体テストは行いません。
テスト用クラスの作成以外の前準備はOkHttpのエミュレータテストと同じです。
:core:dataモジュールのandroidTestにエミュレータテスト用クラスを作成します。
ファイル名をWebViewHtmlRepositoryImplAndroidTestにしています。
package jp.co.example.android01.core.data
import android.content.Context
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*
import kotlinx.coroutines.test.runTest
import jp.co.example.android01.core.data.repository.WebViewHtmlRepositoryImpl
/**
* WebViewHtmlRepositoryImplのAndroid Instrumented Test
* 実際のWebViewクライアントを使用してテストを実行します
*/
@ExtendWith(AndroidJUnit5Extension::class)
@DisplayName("WebViewHtmlRepositoryImpl Android Tests")
class WebViewHtmlRepositoryImplAndroidTest {
private lateinit var context: Context
private lateinit var repository: WebViewHtmlRepositoryImpl
@BeforeEach
fun setUp() {
// テスト用のContextを取得
context = InstrumentationRegistry.getInstrumentation().targetContext
repository = WebViewHtmlRepositoryImpl(context)
Log.i("WebViewAndroidTest", "WebViewHtmlRepositoryImpl initialized")
}
@AfterEach
fun tearDown() {
// クリーンアップ
Log.i("WebViewAndroidTest", "Test cleanup completed")
}
@Nested
@DisplayName("Get HTML Tests")
inner class GetHtmlTests {
@Test
@DisplayName("実際のWebサイトからHTMLコンテンツを取得 - 成功")
fun getHtmlContent_RealWebsite_Success() = runTest {
// Given
val testUrl = "https://httpbin.org/html" // テスト用のHTMLを返すサイト
// When
val result = repository.getHtmlContent(testUrl)
// Then
logLong("=== WebView 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("WebViewAndroidTest", "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, "コンテンツが空でない")
}
@Test
@DisplayName("JavaScriptが有効なページからHTMLコンテンツを取得 - 成功")
fun getHtmlContent_JavaScriptEnabled_Success() = runTest {
// Given
val jsUrl = "data:text/html,<html><head><title>JS Test</title></head><body><h1 id='test'>Original</h1><script>document.getElementById('test').innerHTML='Modified by JS';</script></body></html>"
// When
val result = repository.getHtmlContent(jsUrl)
// Then
logLong("=== JavaScript Test Result ===")
logLong("Success: ${result.isSuccess}")
logLong("Content: ${result.getOrNull()}")
logLong("==============================")
assertTrue(result.isSuccess, "結果は成功であるべき")
assertNotNull(result.getOrNull(), "HTMLコンテンツはnullでない")
// JavaScriptが実行されているかチェック
assertTrue(
result.getOrNull()?.contains("Modified by JS") == true,
"JavaScriptが実行されている"
)
}
}
@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("=== 404 Error Test Result ===")
logLong("Success: ${result.isSuccess}")
logLong("Failure: ${result.isFailure}")
logLong("Exception: ${result.exceptionOrNull()?.message}")
logLong("=============================")
assertTrue(result.isFailure, "結果は失敗であるべき")
assertNotNull(result.exceptionOrNull(), "エラーメッセージが存在する")
}
@Test
@DisplayName("500エラー時 失敗を返す - エラーハンドリング")
fun getHtmlContent_500Error() = runTest {
// Given
val testUrl = "https://httpbin.org/status/500"
// When
val result = repository.getHtmlContent(testUrl)
// Then
logLong("=== 500 Error Test Result ===")
logLong("Success: ${result.isSuccess}")
logLong("Failure: ${result.isFailure}")
logLong("Exception: ${result.exceptionOrNull()?.message}")
logLong("=============================")
assertTrue(result.isFailure, "結果は失敗であるべき")
assertNotNull(result.exceptionOrNull(), "エラーメッセージが存在する")
}
@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("=== WebView 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でもWebViewは何らかのHTMLを返す可能性がある
// 成功または失敗のいずれかが適切に処理されることを確認
assertNotNull(result.getOrNull() ?: result.exceptionOrNull(), "結果は適切に処理されるべき")
}
@Test
@DisplayName("無効なURL - エラーハンドリング")
fun getHtmlContent_InvalidUrl() = runTest {
// Given
val testUrl = "invalid-url-format"
// When
val result = repository.getHtmlContent(testUrl)
// Then
logLong("=== WebView Invalid 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でもWebViewは何らかのHTMLを返す可能性がある
// 成功または失敗のいずれかが適切に処理されることを確認
assertNotNull(result.getOrNull() ?: 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("=== WebView Redirect Test ===")
if (result.isSuccess) {
val content = result.getOrNull() ?: "(no content)"
logLong("Redirect handling successful")
logLong("Content length: ${content.length}")
} else {
Log.e("WebViewAndroidTest", "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
}
}
}
}