- Details
- Written by: Stanko Milosev
- Category: Android
- Hits: 415
var context: Context = Mockito.mock(Context::class.java)
- Details
- Written by: Stanko Milosev
- Category: Android
- Hits: 429
//MockWebServer testImplementation('com.squareup.okhttp3:mockwebserver:5.0.0-alpha.11') testImplementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.11") //RetroFit implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'My example I have to change a little bit to be testable. Api service:
interface IPostSomethingApiService { @Headers("Content-Type: text/json") @POST("/api/UpdateCoordinates") fun postMethod(@Body value: String): Call<String> }In order to be able to test onResponse and onFailure asynchronous callbacks of Retrofit, I have introduced new interface:
interface IPostSomethingCallbacks { fun onSuccess(message: String) fun onFailure(message: String) }Class to accept post:
package com.example.mockwebserverexample import retrofit2.Call import retrofit2.Callback import retrofit2.Response class PostSomething (var postSomethingApiService: IPostSomethingApiService, var postSomethingCallbacks: IPostSomethingCallbacks) { private lateinit var webApiRequest: Call<String> fun myHttpPost(value: String) { webApiRequest = postSomethingApiService.postMethod(value) webApiRequest.enqueue(object : Callback<String> { override fun onResponse(call: Call<String>, response: Response<String>) { if (!response.isSuccessful) { postSomethingCallbacks.onSuccess(response.errorBody()!!.charStream().readText()) } } override fun onFailure(call: Call<String>, t: Throwable) { postSomethingCallbacks.onFailure(t.message.toString()) } }) } }Unit test:
package com.example.mockwebserverexample import junit.framework.TestCase import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before import org.junit.Test //MockWebServer import okhttp3.OkHttpClient import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer //Retrofit import retrofit2.Retrofit import retrofit2.converter.scalars.ScalarsConverterFactory class RetrofitUnitTest { private lateinit var mockWebServer: MockWebServer private lateinit var postSomethingApiService: IPostSomethingApiService private lateinit var postSomething: PostSomething private lateinit var postSomethingCallbacks: PostSomethingCallbacks @Before fun setUp() { mockWebServer = MockWebServer() val client = OkHttpClient.Builder() .build() postSomethingApiService = Retrofit.Builder() .baseUrl(mockWebServer.url("/")) .client(client) .addConverterFactory(ScalarsConverterFactory.create()) .build().create(IPostSomethingApiService::class.java) postSomethingCallbacks = PostSomethingCallbacks() postSomething = PostSomething(postSomethingApiService, postSomethingCallbacks) mockWebServer.start() } @After fun tearDown() { mockWebServer.shutdown() } @Test fun `check if 400 response results in an error state`() { val response = MockResponse() .setBody("400 error test") .setResponseCode(400) mockWebServer.enqueue(response) postSomething.myHttpPost("test") runBlocking { postSomethingApiService.postMethod("test 400") } } } class PostSomethingCallbacks: IPostSomethingCallbacks { override fun onSuccess(message: String) { TestCase.assertEquals("400 error test",message) } override fun onFailure(message: String) { TestCase.assertEquals("400 error test",message) } }Notice code:
postSomething = PostSomething(postSomethingApiService, postSomethingCallbacks)And class where are my asserts:
class PostSomethingCallbacks: IPostSomethingCallbacks { override fun onSuccess(message: String) { TestCase.assertEquals("400 error test",message) } override fun onFailure(message: String) { TestCase.assertEquals("400 error test",message) } }Example download from here. Update: Here is maybe better solution without MockWebServer which gave me ChatGPT:
import com.nhaarman.mockitokotlin2.* import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import retrofit2.Call import retrofit2.Callback import retrofit2.Response class MyCallbackTest { private lateinit var mockedCall: CallGradle:private lateinit var myCallback: MyCallback private lateinit var mockedResponse: Response private lateinit var mockedResponseBody: String @Before fun setup() { mockedCall = mock() myCallback = MyCallback() mockedResponse = mock() mockedResponseBody = "response body" } @Test fun `onResponse success`() { // Given whenever(mockedResponse.isSuccessful).thenReturn(true) whenever(mockedResponse.body()).thenReturn(mockedResponseBody) whenever(mockedResponse.code()).thenReturn(200) // When myCallback.onResponse(mockedCall, mockedResponse) // Then assertEquals(mockedResponseBody, myCallback.responseBody) } @Test fun `onResponse failure`() { // Given val errorResponseBodyString = "error response body" val errorCode = 404 whenever(mockedResponse.isSuccessful).thenReturn(false) whenever(mockedResponse.code()).thenReturn(errorCode) whenever(mockedResponse.errorBody()?.string()).thenReturn(errorResponseBodyString) // When myCallback.onResponse(mockedCall, mockedResponse) // Then assertEquals(errorResponseBodyString, myCallback.errorBody) assertEquals(errorCode, myCallback.errorCode) } private inner class MyCallback : Callback { var responseBody: String? = null var errorBody: String? = null var errorCode: Int? = null override fun onResponse(call: Call , response: Response ) { if (response.isSuccessful) { responseBody = response.body() } else { errorBody = response.errorBody()?.string() errorCode = response.code() } } override fun onFailure(call: Call , t: Throwable) { // Not needed for this test } } }
implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.8.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' // Optional -- Robolectric environment testImplementation 'androidx.test:core:1.6.0-alpha01' // Optional -- Mockito framework testImplementation 'org.mockito:mockito-core:5.2.0' // Optional -- mockito-kotlin testImplementation 'org.mockito.kotlin:mockito-kotlin:4.1.0' // Optional -- Mockk framework testImplementation 'io.mockk:mockk:1.13.4' testImplementation 'junit:junit:4.13.2' testImplementation('com.squareup.okhttp3:mockwebserver:5.0.0-alpha.11') testImplementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.11") // required if you want to use Mockito for Android tests androidTestImplementation 'org.mockito:mockito-android:5.2.0' implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.11' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-scalars:2.9.0' implementation 'com.google.android.gms:play-services-location:21.0.1' implementation 'com.google.code.gson:gson:2.10.1'
- Details
- Written by: Stanko Milosev
- Category: Android
- Hits: 508
using Microsoft.AspNetCore.Mvc; namespace WebApi.Controllers; [Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { [HttpGet] public string Get() { return "test"; } [HttpGet("{id}")] public string Get(int id) { return "value"; } [HttpPost] public string Post([FromBody] string value) { return $"Sent: {value}"; } }Now example in Kotlin. In \app\src\main\AndroidManifest.xml I have added internet permissions:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />In \app\src\main\res\layout\activity_main.xml I have added the button:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/button" android:onClick="postHttp" tools:layout_editor_absoluteX="166dp" tools:layout_editor_absoluteY="441dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> /> </androidx.constraintlayout.widget.ConstraintLayout>In \app\build.gradle I have added
implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.okhttp3:okhttp:4.9.0" implementation 'com.squareup.retrofit2:converter-scalars:2.1.0'Code to trust all certificates taken from here.
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager { override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) { Log.i(MainActivity::class.simpleName, "checkClientTrusted") } override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) { Log.i(MainActivity::class.simpleName, "checkServerTrusted") } override fun getAcceptedIssuers() = arrayOf<X509Certificate>() }) val sslContext = SSLContext.getInstance("SSL") sslContext.init(null, trustAllCerts, java.security.SecureRandom()) // Create an ssl socket factory with our all-trusting manager val sslSocketFactory = sslContext.socketFactory // connect to server val client = OkHttpClient.Builder().sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager).hostnameVerifier{ _, _ -> true }.build()Next building Retrofit:
val retro = Retrofit.Builder() .baseUrl("https://10.0.2.2:7037") .client(client) .addConverterFactory(ScalarsConverterFactory.create()) .build()Here notice baseUrl: https://10.0.2.2:7037 - 10.0.2.2 is localhost for Android Studio emulator Add WebApiService interface:
interface WebApiService { @Headers("Content-Type: text/json") @POST("/api/Values") fun postMethod(@Body value: String): Call<String> }Create request:
val service = retro.create(WebApiService::class.java) val webApiRequest = service.postMethod("\"test\"")Here notice "\"test\"" since I am trying to send a raw string, otherwise would be rejected. Last but not least, onResponse and onFailure:
val alertDialogBuilder = AlertDialog.Builder(this@MainActivity) webApiRequest.enqueue(object : Callback<String> { override fun onResponse(call: Call<String>, response: Response<String>) { if (!response.isSuccessful) { alertDialogBuilder.setMessage(response.errorBody()!!.charStream().readText()) .setCancelable(false) .setNeutralButton("OK", DialogInterface.OnClickListener { dialog, _ -> dialog.dismiss() }) val alert = alertDialogBuilder.create() alert.setTitle("Error") alert.show() } else { alertDialogBuilder.setMessage("Response: ${response.body().toString()}") .setCancelable(false) .setNeutralButton("OK", DialogInterface.OnClickListener { dialog, _ -> dialog.dismiss() }) val alert = alertDialogBuilder.create() alert.setTitle("Error") alert.show() } } override fun onFailure(call: Call<String>, t: Throwable) { alertDialogBuilder.setMessage(t.message) .setCancelable(false) .setNeutralButton("OK", DialogInterface.OnClickListener { dialog, _ -> dialog.dismiss() }) val alert = alertDialogBuilder.create() alert.setTitle("Error") alert.show() } }).NET solution download from here and Android source code download from here here.
- Details
- Written by: Stanko Milosev
- Category: Android
- Hits: 875
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />Then in the MainActivity I have added the method "checkOptimization" and "openBatteryOptimization ":
@SuppressLint("NewApi", "BatteryLife") private fun checkOptimization(context: Context) { val packageName = applicationContext.packageName val pm = applicationContext.getSystemService(POWER_SERVICE) as PowerManager if (!pm.isIgnoringBatteryOptimizations(packageName)) { val intent = Intent() intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent.data = Uri.parse("package:" + context.packageName) context.startActivity(intent) } } fun openBatteryOptimization(context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val intent = Intent() intent.action = Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS context.startActivity(intent) } else { //Timber.d("Battery optimization not necessary") } }Both methods "checkOptimization" and "openBatteryOptimization" I have added to "onCreate" of MainActivity. For
Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGSIn import list I have added:
import android.provider.Settings import android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONSIn Gradle I added:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1"Service and receiver first I have copied from original article, then I have added following methods:
private val wakeLock: PowerManager.WakeLock by lazy { (getSystemService(Context.POWER_SERVICE) as PowerManager).run { newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "neverEndingApplication:ServiceWakelock") } } private fun acquireWakelock() { try { wakeLock.let { wakeLock.setReferenceCounted(false) if (!wakeLock.isHeld) { wakeLock.acquire() } } } catch (e: RuntimeException) { } } private fun releaseWakelock() { try { wakeLock.let { if (it.isHeld) { it.release() } } } catch (e: RuntimeException) { } }Still in Service I have added acquireWakelock in onCreate:
override fun onCreate() { super.onCreate() acquireWakelock() }releaseWakelock() I have added when I want to stop the app:
if (intent?.action.equals("stopForeground")) { job?.cancel() releaseWakelock() stopForeground(true) stopSelfResult(startId) }MainActivity I also copied from my original article. Example download from here.