A step-by-step guide using MockWebServer on Android Jetpack Compose UI tests to fix java.net.UnknownServiceException issue
When writing Android UI tests, you might run into this error,
java.net.UnknownServiceException: CLEARTEXT
communication to localhost not permitted by network security policy
This happens because modern Android versions, since Android 9 (API level 28), enforce stricter network security policies, preventing unencrypted (HTTP) communication. The changes also affect MockWebServer
which uses a Localhost connection to mock API responses in Android instrumentation tests.
In this article, I’ll show you how to properly set up the MockWebServer
for Android UI tests and configure the app to bypass this network security restriction.
Before starting, make sure the MockWebServer
dependency has been installed.
[versions]
okhttp = "4.12.0"[libraries]
okhttp-mockwebserver = { group = "com.squareup.okhttp3", name = "mockwebserver", version.ref = "okhttp" }
There are 2 options to fix the issue,
- Use network security config
- Enable HTTPS on
MockWebServer
Create network_security_config.xml
file inside debug/res/xml/
directory and add the localhost domain there.
localhost
127.0.0.1
- Make sure to create the file inside the
debug
directory, notmain
directory. So this network security bypass does not affect the production app.
Then register the network security config to the debug/AndroidManifest.xml
.
...
android:networkSecurityConfig="@xml/network_security_config">
...
- Again, make sure to register the config to
debug
variantAndroidManifext.xml
, so this network security bypass does not affect the production app.
Install OkHttp-TLS
dependency.
[versions]
okhttp = "4.12.0"[libraries]
okhttp-tls = { group = "com.squareup.okhttp3", name = "okhttp-tls", version.ref = "okhttp" }
Create an SSL certificate using HeltCertificate
.
val heldCertificate = HeldCertificate.Builder()
.commonName("localhost")
.addSubjectAlternativeName("localhost")
.build()
val certificates = HandshakeCertificates.Builder()
.heldCertificate(heldCertificate)
.addTrustedCertificate(heldCertificate.certificate)
.build()
Set up the MockWebServer
with HTTPS.
MockWebServer().apply {
// enable https on mockwebserver
// use the HandshakeCertificates created above
useHttps(certificates.sslSocketFactory(), false)
}
Update the OkHttpClient
to trust the certificate.
val okHttpClient = OkHttpClient.Builder()
// use the HandsakeCertificates created above
.sslSocketFactory(
certificates.sslSocketFactory(),
certificates.trustManager
)
.build()
Then set the OkHttpClient
to the Retrofit
and update the base URL with the MockWebServer
URL.
val retrofit = Retrofit.Builder()
// change the base url with the one from mockwebserver
.baseUrl(mockWebServer.url("/").toString())
// set the okHttpClient
.client(okHttpClient)
.build()
Option 1: Using network security config in debug variant
- ✅ Fast and simple to implement.
- ✅ No need to modify API clients (
OkHttp
andRetrofit
). - ❌ Not a real-world simulation, because production apps should use HTTPS.
- ❌ Potential risk if misconfigured (eg. accidentally included in the production app).
- ❌ Only works for the domain registered in the network security config.
Option 2: Enabling HTTPS on MockWebServer
- ✅ More secure, aligns with the production app behavior.
- ✅ Mimics real-world HTTPS usage, ensuring better test coverage.
- ✅ No need to modify the app’s security policies.
- ❌ More complex setup (requires setting up a self-signed certificate).
- ❌ Requires modifying the API client (OkHttp needs to trust the self-signed certificate).
- ❌ Can introduce flakiness if SSL setup is not handled correctly.
Enabling HTTPS on MockWebServer
more complex to set up, especially if you are using dependency injection with Hilt in your app. But, I’d still recommend enabling HTTPS on MockWebServer
to do real-world HTTPS testing.