#ParameterizedTest in UI test JetpackCompose Android - android

I have a view that builds in JetpackCompose, I need to run the UI test in Android Studio by ParameterizedTest.
I run a simple ParameterizedTest in the below class but the Android Studio does not show successful state and I got this error.
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import io.mockk.spyk
import junit.framework.Assert.assertTrue
import org.junit.Rule
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
class MainTest {
#get:Rule
val composeTestRule = createComposeRule()
#ParameterizedTest
#ValueSource(ints = [2, 4, 6])
fun testIsEven(i: Int) {
assertTrue(i % 2 == 0)
}
}
Error:
1. No runnable methods
at org.junit.runners.ParentRunner.validate(ParentRunner.java:525)
at org.junit.runners.ParentRunner.<init>(ParentRunner.java:92)
at org.junit.runners.BlockJUnit4ClassRunner.<init>(BlockJUnit4ClassRunner.java:74)
at androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner.<init>(AndroidJUnit4ClassRunner.java:43)
at androidx.test.internal.runner.junit4.AndroidJUnit4Builder.runnerForClass(AndroidJUnit4Builder.java:65)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
Can I run ParameterizedTest in UI test?

Related

How to test a Jetpack Compose Composable containing rememberLauncherForActivityResult?

How can I test an Android Jetpack Compose composable that calls rememberLauncherForActivityResult?
For example:
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
import androidx.compose.runtime.Composable
import androidx.compose.material3.Button
import androidx.compose.material3.Text
#Composable
fun PickFileButton() {
val launcher = rememberLauncherForActivityResult(CreateDocument("text/plain")) {
/* do something with the returned Uri */
}
Button(onClick={launcher.launch("default_file_name.txt")}) {
Text("Pick File")
}
}
Clicking the button launches another activity that the user uses to pick a file.
A test would look like:
import org.junit.Rule
import org.junit.Test
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
class TestPickFileButton {
#get:Rule val composeTestRule = createComposeRule()
#Test
fun testPickFileButton() {
composeTestRule.setContent { PickFileButton() }
composeTestRule.onNodeWithText("Pick File").performClick()
// now Android CreateDocument activity has launched
// and "composeTestRule" cannot interact with this activity
// rest of test eg to check the file was created
}
}
I cannot complete the test because the external activity has been launched and I can't interact with this activity with the ComposeContentTestRule object.
The solution involves using CompositionLocalProvider to override the default ActivityResultRegistry. This allows you to send a custom response with ActivityResultRegistry.dispatchResult instead of launching an activity.
I had to read the source code for the CreateDocument contract to understand what kind of Intent was expected to be returned.
A working solution for this particular question is:
import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.activity.compose.LocalActivityResultRegistryOwner
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResultRegistry
import androidx.activity.result.ActivityResultRegistryOwner
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.core.app.ActivityOptionsCompat
import androidx.core.net.toFile
import org.junit.Rule
import org.junit.Test
#Composable
fun PickFileButton() {
val launcher =
rememberLauncherForActivityResult(CreateDocument("text/plain")) {
/* do something with the returned Uri */
}
Button(onClick = { launcher.launch("default_file_name.txt") }) { Text("Pick File") }
}
class TestPickFileButton {
#get:Rule val composeTestRule = createComposeRule()
#Test
fun testPickFileButton() {
val testUri = Uri.parse("uri_string_to_return")
composeTestRule.setContent {
// ActivityResultRegistry is responsible for handling the
// contracts and launching the activity
val registryOwner = ActivityResultRegistryOwner {
object : ActivityResultRegistry() {
override fun <I : Any?, O : Any?> onLaunch(
requestCode: Int,
contract: ActivityResultContract<I, O>,
input: I,
options: ActivityOptionsCompat?
) {
// don't launch an activity, just respond with the test Uri
val intent = Intent().setData(testUri)
this.dispatchResult(requestCode, Activity.RESULT_OK, intent)
}
}
}
CompositionLocalProvider(LocalActivityResultRegistryOwner provides registryOwner) {
// any composable inside this block will now use our mock ActivityResultRegistry
PickFileButton()
}
}
composeTestRule.onNodeWithText("Pick File").performClick()
// no activity is launched, PickFileButton will received testUri as a respose.
// rest of test eg to check the file was created
assert(testUri.toFile().exists())
}
}
Sources:
Discussion in Kotlin Lang Slack Chat,
ActivityResultRegistryTest's in Android repository,
CompositionLocalProvider

Why android API:getNotificationPolicy can't run successfully on some emulator with specific sdk version?

The Unit Test code is attached:
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
#RunWith(AndroidJUnit4.class)
public class TestCase_com_cabooze_buzzoff__1592700345 {
#Test
public void testCase() throws Exception {
Context var1 = InstrumentationRegistry.getTargetContext();
Object var2 = var1.getSystemService("notification");
NotificationManager var3 = (NotificationManager)var2;
Policy var4 = var3.getNotificationPolicy();
}
}
I run the above code on sdk 21-29 and the results are :
21-22:NoSuchMethodError
23-27:SecurityException
28-29:success
The minSDKVersion of my apk is 21; targetSDKVersion is 30.
I am wondering why getNotificationPolicy will throw SecurityException on 23-27, considering the google doc yield that getNotificationPolicy need android.permission.ACCESS_NOTIFICATION_POLICY which is a normal permission. Besides, why it can run successfully on 28-29.

How to use Foreground Service in Android?

Can anyone please help. Am a noob to Android studio. My first major app, is a streaming media player. Problem, i wish to use foreground service, but i just cannot understand how to implement foreground service. I tried searching youtube, but not much on kotlin, and the same with the sites. Can anyone please give me a step by step guide for this.
This is my MainActivity below. The app is working, just missing the foreground service.
package com.example.al_bunyan
import android.app.Dialog
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.AnimationDrawable
import android.media.MediaPlayer
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.VideoView
import java.time.Instant
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var web_btn = findViewById<Button>(R.id.link_btn)
web_btn.setOnClickListener {
var intent = Intent(this#MainActivity, AlbunyaWebview::class.java)
startActivity(intent)
}
}
fun video_check(view:View) {
val fm105 = findViewById<Button>(R.id.fm105_7)
val alert = Dialog(this#MainActivity)
alert.setContentView(R.layout.audio_play)
val video = alert.findViewById<VideoView>(R.id.video_alert)
val play = alert.findViewById<Button>(R.id.play)
val pause = alert.findViewById<Button>(R.id.pause)
val resume = alert.findViewById<Button>(R.id.resume)
if (fm105.isPressed) {
val video_1 = Uri.parse("http://albunyan.fm:8000/live")
video.setVideoURI(video_1)
alert.show()
play.setOnClickListener {
video.start()
}
pause.setOnClickListener {
video.pause()
}
resume.setOnClickListener {
video.start()
}
}
}
}
There are many resources available including code examples.
following are some of them
Android developer docs Services
Android developer docs Foreground services
Sample Android project on github LocationUpdatesForegroundService
I suggest first you read some theory from the first two resources and then try to learn from the code in the github project, which clearly shows how you can implement foreground service.

How to prevent screen-record in an flutter-based application on iOS and android?

Our app needs to prevent users to record our screen for some important reasons. How to prevent screen-record on iOS and android?
It seems that we can get a call-back if users are recording the screen after iOS 11. Is there any method to use that call-back to realize screen-record preventing?
That is something which is not completely possible. There are 2 scenarios for Android OS
1 - When Android device is not rooted - You can use flag FLAG_SECURE which will prevent screenshot and video recording. You can read further here https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_SECURE
2 - When Android device is rooted - You can have a check programatically to know if device is rooted or not. If it is rooted then you can stop your application from running further. Here is the link to check for root device - Determining if an Android device is rooted programmatically?
Put this code in your MainActivity.kt and You're done :)
(The code isn't mine, I found it on Github)
import io.flutter.embedding.android.FlutterActivity
import android.os.Build
import android.view.ViewTreeObserver
import android.app.ActivityManager
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.view.SurfaceView
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.WindowManager.LayoutParams
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val flutter_native_splash = true
var originalStatusBarColor = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
originalStatusBarColor = window.statusBarColor
window.statusBarColor = 0xffeaeaea.toInt()
}
val originalStatusBarColorFinal = originalStatusBarColor
if (!setSecureSurfaceView()) {
Log.e("MainActivity", "Could not secure the MainActivity!")
// React as appropriate.
}
getWindow().addFlags(LayoutParams.FLAG_SECURE)
getWindow().setFlags(LayoutParams.FLAG_SECURE,
LayoutParams.FLAG_SECURE)
SurfaceView(applicationContext).setSecure(true)
}
private fun setSecureSurfaceView(): Boolean {
val content = findViewById<ViewGroup>(android.R.id.content)
if (!isNonEmptyContainer(content)) {
return false
}
val splashView = content.getChildAt(0)
if (!isNonEmptyContainer(splashView)) {
return false
}
val flutterView = (splashView as ViewGroup).getChildAt(0)
if (!isNonEmptyContainer(flutterView)) {
return false
}
val surfaceView = (flutterView as ViewGroup).getChildAt(0)
if (surfaceView !is SurfaceView) {
return false
}
surfaceView.setSecure(true)
this.window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
return true
}
private fun isNonEmptyContainer(view: View): Boolean {
if (view !is ViewGroup) {
return false
}
if (view.childCount < 1) {
return false
}
return true
}
}
You can use the flutter_windowmanager plugin. Once you have added to your pubspec.yaml file then import it where you want to disable screenshot. Using
import 'package:flutter_windowmanager/flutter_windowmanager.dart';
Now add this line into your Stateful Widget.
Future<void> disableScreenShot() async {
await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
}
#override
void initState() {
disableScreenShot();
super.initState();
}
Now Normal Users can't take screenshots.

Testing RxWorker for WorkManager

I want to try out work manager for the first time. I am used to rxJava so I decided to implement my work manager using RxWorker. But the testing aspect is giving me headache.Basically, the work manager checks firebase to get latest changes to latest changes to particular document (This is not the best use case I know).But the problem is in the test returns without waiting for success or failure.It returns when the work manager is still running.
This is my work manager implementation
class MidiSyncWorker(context: Context, params: WorkerParameters) : RxWorker(context, params) {
override fun createWork(): Single<Result> {
return Injection.provideSharePrefsRepo.midiArchiveVersion()
.flatMapObservable { currentVersion ->
Injection.provideOnlineRepo.latestMidiArchive()
.filter { onlineMidi -> onlineMidi.version > currentVersion }
}.firstOrError()
.map { onlineMidi ->
val outputData = Data.Builder()
.putString(KEY_FIREBASE_ARCHIVE_PATH, onlineMidi.url)
Result.success(outputData.build()) }
}
.onErrorReturn { Result.failure() }
}
This is my test
fun midiSyncVersionCheck_success_onlineVersionDiffersFromLocalVersion() {
// create request
val request = OneTimeWorkRequestBuilder<MidiSyncWorker>()
.build()
wmRule.workManager.enqueue(request).result.get()
val workInfo = wmRule.workManager.getWorkInfoById(request.id).get(10, TimeUnit.SECONDS)
assertThat(workInfo.state, `is`(WorkInfo.State.SUCCEEDED))
}
I expected the test to wait until workmanager returns success or failure. But it returns while work manager is still running
java.lang.AssertionError:
Expected: is <SUCCEEDED>
but: was <RUNNING>
WorkManager makes available a couple of ways to test your Worker classes. You can find all the details on WorkManager Testing documentation page.
The original WorkManagerTestInitHelper only supports Worker classes, meanwhile, the newly introduce in (WorkManager v2.1.0-alpha01) TestListenableWorkerBuilder can be used to test both ListenableWorker classes and the other classes that derives from it (like CoroutineWorker and RxWorker.
In your particular case, you should be able to do:
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.work.ListenableWorker.Result
import androidx.work.testing.TestListenableWorkerBuilder
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assert.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
#RunWith(JUnit4::class)
class MyWorkTest {
private lateinit var context: Context
#Before
fun setup() {
context = ApplicationProvider.getApplicationContext()
}
#Test
fun testMidiSyncWorker() {
// Get the ListenableWorker
val worker = TestListenableWorkerBuilder<MidiSyncWorker>(context).build()
// Start the work synchronously
val result = worker.startWork().get()
assertThat(result, `is`(Result.success()))
}
}
In this way you're calling synchrously your worker class.
In this case you need to use the as a test dependency in your build.gradle file:
def work_version = "2.1.0-alpha02"
androidTestImplementation "androidx.work:work-testing:$work_version"
You can find a similar, complete, sample (for a CoroutineWorker), on the Kotlin's coroutine Codelab.

Categories

Resources