5

I got above error when i use mockhttpserver to test http request, the following code is how i create service.

   fun <T> getService(clazz: Class<T>): T {
        val client = OkHttpClient.Builder().addInterceptor(HeaderInterceptor()).addInterceptor(HttpLoggingInterceptor().apply { level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE })
                .build()
        return Retrofit.Builder()
                .baseUrl(mockWebServer.url(""))
                .client(client)
                .addConverterFactory(GsonConverterFactory.create(GSON))
                .build()
                .create(clazz)
    }

This is my Test code.

@UninstallModules(HomePageDataModule::class)
@HiltAndroidTest
class TestHomePageViewModel {


    private lateinit var viewModel: HomePageViewModel

    @get:Rule
    var hiltRule = HiltAndroidRule(this)

    @Inject
    lateinit var cpd: CompositionDao

    @Inject
    lateinit var csd: CompositionsDao

    @Inject
    lateinit var hpds: HomePageDataSource

    @Inject
    lateinit var ss :HomePageService

    @Before
    fun init() {
        hiltRule.inject()
        viewModel = HomePageViewModel(HomeCompositionsRepository(cpd, csd, hpds, Util.GSON))

    }





    @Test
    fun testObserveHomeData() {

        val data = Util.getFileString("mainpage.json")
        val rr  = GSON.fromJson(data,Array<HomePreviewView>::class.java)
        println(rr)
        enqueueResponse("mainpage.json")
        runBlocking {
            val result = ss.getHomeData()
            Assert.assertNotEquals(rr.size,result.body()!!.size)
        }
}

Everything works smoothly on my app except running my unit test code. There is a similar problem , but my issue has a little difference compare to that one. Plenty of ways from that similar question i have tried, but not work.

PS: If the test code run on Junit4Test but not AndroidJunit4Test, it works properly. But now i need to exectue a integrate test. So this part of code need to be executed on AndroidJunit4Test

Quick learner
  • 9,144
  • 3
  • 37
  • 51
Cyrus
  • 7,847
  • 8
  • 28
  • 55
  • 1
    Make sure your host is secure for your application or you can just add this line of code inside your manifest file. android:usesCleartextTraffic="true" – aslamconsole Jul 22 '20 at 10:22
  • @AslamHossin I have no idea why it does not work for me when i execute above unit test code. – Cyrus Jul 22 '20 at 10:26
  • https://stackoverflow.com/questions/53984725/networksecurityconfig-no-network-security-config-specified-using-platform-defa/53984915#53984915 – Quick learner Jul 22 '20 at 10:34
  • Does this answer your question? [Android 8: Cleartext HTTP traffic not permitted](https://stackoverflow.com/questions/45940861/android-8-cleartext-http-traffic-not-permitted) – Sina Jul 22 '20 at 10:41
  • 2
    @Cyrus When the attribute is set to "false", platform components (for example, HTTP and FTP stacks, DownloadManager, and MediaPlayer) will refuse the app's requests to use cleartext traffic. Third-party libraries are strongly encouraged to honor this setting as well. The key reason for avoiding cleartext traffic is the lack of confidentiality, authenticity, and protections against tampering; a network attacker can eavesdrop on transmitted data and also modify it without being detected. More https://developer.android.com/guide/topics/manifest/application-element#usesCleartextTraffic – aslamconsole Jul 22 '20 at 10:42
  • 1
    You can also use mockWebServer.useTls and client.insecureHost to use SSL for the test connections. – Yuri Schimke Jul 23 '20 at 07:23

3 Answers3

18

Solution 1)

Add the following attribute to the <application tag in AndroidManifest.xml:

android:usesCleartextTraffic="true"

Solution 2)

Add android:networkSecurityConfig="@xml/network_security_config" to the <application tag in app/src/main/AndroidManifest.xml:

<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:networkSecurityConfig="@xml/network_security_config"
        android:theme="@style/AppTheme">

With a corresponding network_security_config.xml in app/src/main/res/xml/:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>
Quick learner
  • 9,144
  • 3
  • 37
  • 51
1

Problem finally solved according to @Yuri Schimke prompt.

First step: mockwebserver use https

 val mockWebServer = MockWebServer().apply {
        val localhost: String = InetAddress.getByName("localhost").canonicalHostName
        val localhostCertificate: HeldCertificate = HeldCertificate.Builder().addSubjectAlternativeName(localhost)
                .build()
        val serverCertificates = HandshakeCertificates.Builder()
                .heldCertificate(localhostCertificate)
                .build()
        useHttps(serverCertificates.sslSocketFactory(), false)
    }

Second step: use unsafe okhttp client

  private fun getUnsafeOkHttpClient(): OkHttpClient.Builder? {
        return try {
            // Create a trust manager that does not validate certificate chains
            val trustAllCerts: Array<TrustManager> = arrayOf<TrustManager>(
                    object : X509TrustManager {
                        @Throws(CertificateException::class)
                        override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) {
                        }

                        @Throws(CertificateException::class)
                        override fun checkServerTrusted(chain: Array<X509Certificate?>?, authType: String?) {
                        }

                        override fun getAcceptedIssuers(): Array<X509Certificate> {
                            return arrayOf()
                        }


                    }
            )

            // Install the all-trusting trust manager
            val sslContext: SSLContext = SSLContext.getInstance("SSL")
            sslContext.init(null, trustAllCerts, SecureRandom())

            // Create an ssl socket factory with our all-trusting manager
            val sslSocketFactory: SSLSocketFactory = sslContext.getSocketFactory()
            val builder: OkHttpClient.Builder = OkHttpClient.Builder()
            builder.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
            builder.hostnameVerifier(HostnameVerifier { hostname, session -> true })
            builder
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }

After finished this, you can create your own service as following code.

fun <T> getService(clazz: Class<T>): T {
        val client =getUnsafeOkHttpClient()!!.addInterceptor(HeaderInterceptor())
                .addInterceptor(HttpLoggingInterceptor().apply { level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE })
                .build()
        return Retrofit.Builder()
                .baseUrl(mockWebServer.url(""))
                .client(client)
                .addConverterFactory(GsonConverterFactory.create(GSON))
                .build()
                .create(clazz)
    }

Hope this is helpful if you have okhttp unit tests on AndroidJunit4

Cyrus
  • 7,847
  • 8
  • 28
  • 55
1

In my case, I added this to the android manifest file:

android:usesCleartextTraffic="true"

Tugay
  • 1,950
  • 5
  • 14
  • 30