I am new to the Mockito Android Test framework. I have written one method in the activity and which shows the AlertDialog and I am writing test cases for that method.
Here are the functions of the activity which shows the alert dialog box:
fun showDialogBox(
shortMessage: String?,
longMessage: String?,
progress: Int,
status: String?
) {
runOnUiThread {
if (cdfwExtractionStatusAlertDialog == null) {
val mDialogView = LayoutInflater.from(this).inflate(
R.layout.cdfw_extraction_status_layout,
null
)
val mBuilder = AlertDialog.Builder(this)
.setView(mDialogView)
.setCancelable(false)
shortMessageTv = mDialogView.findViewById(R.id.short_message_tv)
longMessageTv = mDialogView.findViewById(R.id.long_message_tv)
closeButton = mDialogView.findViewById(R.id.close_button)
cdfwProcessBar = mDialogView.findViewById(R.id.progress_bar)
cdfwProcessBar.progress = progress
shortMessageTv.text = shortMessage
longMessageTv.text = longMessage
closeButton.setOnClickListener {
cdfwExtractionStatusAlertDialog?.dismiss()
}
checkStatus(status)
cdfwExtractionStatusAlertDialog = mBuilder.show()
} else {
shortMessageTv.text = longMessage
longMessageTv.text = longMessage
cdfwProcessBar.progress = progress
checkStatus(status)
}
}
}
Here are my mockito test cases for the same function:
#Test
public void testCDFWExtractionStatusNoZipFound(){
MainActivity activity = Mockito.mock(MainActivity.class);
activity.showDialogBox("","CDFW Required.",0,"FAILED_NO_ZIP_FOUND");
Mockito.matches("CDFW Extraction Status");
Mockito.matches("CDFW Required.");
Mockito.matches("Close");
}
#Test
public void testCDFWExtractionStatusInProgress(){
MainActivity activity = Mockito.mock(MainActivity.class);
activity.showDialogBox("","CDFW extraction ongoing",0,"IN_PROGRESS");
Mockito.matches("CDFW Extraction Status");
Mockito.matches("CDFW extraction ongoing");
}
It shows the test passed successfully but I am not sure whether this is the right way to write test cases for the same function if this is the wrong method then how do I write a test case for the same function.
When you are testing your UI it is better to use instrumented testing.
Instrumentation testing is testing that runs on Physical devices, so, in your case, the test will run in a real device or emulator to check if the flow is right.
For that, you can use Espresso, which is a framework provided by AndroidX.
For more detail, please check this out:
Test UI for a single app
Now, why not use Mockito for UI testing?
When you are testing the UI, you have to ensure the right flows to the user and good interaction. You can mock the scenario, but you can't test the whole flow because you don't know how it will behave when, for example, a click is made in a different place than the one you are testing on.
If you want to test for example that your Alert Dialog is shown, you can use:
#Test
fun testCDFWExtractionStatusNoZipFound() {
//..
onView(withId(R.id.dialogId)).check(matches(allOf(withText("CDFW Required"), isDisplayed()));
//..
}
Related
I need to check if the Android phone my app runs on is using casting which is enabled outside of my app.
It seems CastSession or SessionManager can provide the session related to my app which is not helpful for me.
For example, I can start casting with an app called xx which will cast or mirror the entire screen of my phone. Now, I need to notify when I open my app that the phone's screen is casting/mirroring so I can prevent showing specific content on my app.
I checked it with the code below:
val isCastingEnabledLiveData = MutableLiveData<Boolean>()
fun isCastingEnabled(context: Context): Boolean {
val mediaRouter = MediaRouter.getInstance(context)
if (mediaRouter.routes.size <= 1) {
isCastingEnabledLiveData.value = false
return
}
val selector = MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build()
mediaRouter.addCallback(selector, object : MediaRouter.Callback() {
override fun onRouteChanged(router: MediaRouter?, route: MediaRouter.RouteInfo?) {
super.onRouteChanged(router, route)
isCastingEnabledLiveData.value = if (route != mediaRouter.defaultRoute) {
route?.connectionState != MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED
} else false
}
})
}
you can check whether the phone screen is casting or not by using the MediaRouter class.
Here is an example of how you could check if the phone screen is casting:
MediaRouter mediaRouter = (MediaRouter)
getSystemService(Context.MEDIA_ROUTER_SERVICE);
MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
if(route.isDefault()){
// Screen is not casting
} else {
// Screen is casting
}
This code uses the getSelectedRoute() method of the MediaRouter class to get the currently selected route. If the returned RouteInfo object is the default route, then the screen is not casting, otherwise it is.
Please note that this code uses the getSystemService(Context.MEDIA_ROUTER_SERVICE) method to get an instance of the MediaRouter class, so it should be called from an Activity or Service context.
Additionally, you could also use MediaRouter.Callback and MediaRouter.addCallback to set a callback to monitor the state of the casting, so you could get the updates on the casting state change as well.
My Flutter app uses the native Android library.
That is, it uses the MethodChannel.
Now, I want to test the result of calling the native library's processing via the MethodChannel.
Since the MethodChannel requires calling Java processes, the test should be an Integration Test, not a Unit Test.
How can I run any method in Integration Test on my app?
Here is my test code.
// app.dart
void main() {
enableFlutterDriverExtension();
app.main();
}
// app_test.dart
void main() {
FlutterDriver driver;
group("MyTests", () {
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null) {
driver.close();
}
});
test("Test1", () async {
var result1 = await CallNativeMethodWithMethodChannel.method1(value: -1);
expect(false, result1);
var result2 = await CallNativeMethodWithMethodChannel.method1(value: 0);
expect(false, result2);
var result3 = await CallNativeMethodWithMethodChannel.method1(value: 1);
expect(true, result3);
});
});
}
When this test code is executed, result1, result2, and result3 will always be null. This means that the method is not executed on the app.
How can I execute method1 on my app?
(Of course, I know that it is possible to test if I prepare a button on the app screen to run the test and tap it from the test code, but in reality, we have more than 100 items to test and it is not practical to prepare more than 100 buttons.)
Note :
I need to test the result of a native method call for a reason.
In other words, it is not appropriate to mock the execution result of a native method.
Also, I want to test a combination of Dart code and native methods, so I am not testing "only" the native methods.
I am uploading data to a webserver. On my fragment I have a button to start the upload. There are two phases what I am trying to have the user notification done via a none-cancellable AlertDialog solution.
When I am pressing the upload button, preparation for the upload is starting I am setting up the AlertDialog and presenting it. Once the physical upload is starting, I am using the same AlertDialog, but changing the message in it to show the progress of the upload.
***** Now the issue is the following ******
When I setup the AlertDialog and call the Show method, it does not display the AlertDialog. But once the upload is started and the progress is updated I just call the setMessage method and at this point the AlertDialog appears.
The relevant codes are the followings:
The submitbutton.setOnClickLictener is in the onViewCreated()
submitbutton.setOnClickListener {
requireActivity().runOnUiThread {
SubmitAd()
}
}
I have tried here the run the SubmitAd() on the UIThread, if it helps, but it is the same without it.
SubmitAd is showing the Dialog. (Actually at this point nothing is shown.
fun SubmitAd() {
var addInApp: Boolean = false
ToBePurchased = 0
if (CheckCanUpload()) {
var AlertView = AlertDialog.Builder(requireActivity())
AlertView.setTitle("Hirdetés feltöltés")
AlertView.setMessage("A feltöltés előkészítése hosszabb ideig is eltarhat, kérjük várjon!")
AlertView.setCancelable(false)
DialogToShow = AlertView.create()
DialogToShow!!.show()
purchaseLoop = 0
UploadWithPurchase()
} else {
var AlertView = AlertDialog.Builder(requireActivity())
AlertView.setTitle("Hirdetés hiba")
AlertView.setMessage("A hirdetése hiányos. Kérjük töltse ki az összes mezőt és csatoljon fotót a hirdetéséhez!")
AlertView.setPositiveButton("Ok") { dialog, which ->
dialog.dismiss()
}
DialogToShow = AlertView.create()
DialogToShow!!.show()
}
}
In UploadWithPurchase() the Playstore purchase handling is done, but if there is no purchase at all, it is just going through a loop, which calls UploadWithPurchase() recursively until all possible purchases are checked, then it goes to the real Upload() which calls an Http request to upload the data and reports back via an interface the progress of the upload process.
The Webhelper returns the progress like this:
override fun WebHelperProgress(id: String, progress: Float) {
if (DialogToShow != null) {
DialogToShow!!.setMessage("Feltöltés folyamatban. Kérem várjon! ... ${progress.toInt()}%")
}
}
When this method is called, the AlertDialog appears.
Whatever I have tried, does not help. AlertDialog does not show up at the first call, but no clue why.
EDIT later: I have figured out that the AlertDialog is actually appears once it comes out from the recursive loop, but I do not know how to force it to be displayed before it starts the loop. That would be my aim to notify the user that a longer process is starting. It meaningless to start the process and the user does not know what is happening.
Finally I could solve it by putting the purchaseLoop to a separate Thread like this.
fun SubmitAd() {
var addInApp: Boolean = false
ToBePurchased = 0
if (CheckCanUpload()) {
var AlertView = AlertDialog.Builder(requireActivity())
AlertView.setTitle("Hirdetés feltöltés")
AlertView.setMessage("A feltöltés előkészítése hosszabb ideig is eltarhat, kérjük várjon!")
AlertView.setCancelable(false)
DialogToShow = AlertView.create()
DialogToShow!!.show()
purchaseLoop = 0
******** SOLUTION HERE ********
Thread {
UploadWithPurchase()
}.start()
*******************************
} else {
var AlertView = AlertDialog.Builder(requireActivity())
AlertView.setTitle("Hirdetés hiba")
AlertView.setMessage("A hirdetése hiányos. Kérjük töltse ki az összes mezőt és csatoljon fotót a hirdetéséhez!")
AlertView.setPositiveButton("Ok") { dialog, which ->
dialog.dismiss()
}
DialogToShow = AlertView.create()
DialogToShow!!.show()
}
}
I have a test case where in the app "Set as default" prompt is opened. I want to test that with UI automator, and I had success with testing that case, but not 100% reliable. Unfortunately, some devices have "Set as default" prompt button written in caps, and some of those don't, so I'm not able to create 100% reliable tests for this test case. I have written this code below, but when fetching "Set as default" button by text, case of the letters don't play a role, but when I want to interact with that button, text case is important. Switching the IF-ELSE cases doesn't fix the problem in this case. And somehow, none of the dialog buttons ids work (button1, button2..) when I want to press those.
if (roleManager.isRoleAvailable(android.app.role.ASSISTANT)) {
if (!roleManager.isRoleHeld(android.app.role.ASSISTANT)) {
val myApp = device.findObject(UiSelector().textMatches(InstrumentationRegistry.getInstrumentation().targetContext.getString(R.string.app_name)))
myApp.click()
sleepLong()
var setAsDefaultButton: UiObject? = null
if (device.findObject(UiSelector().text("Set as default")) != null) {
setAsDefaultButton = device.findObject(UiSelector().text("Set as default"))
setAsDefaultButton?.click()
} else if (device.findObject(UiSelector().text("SET AS DEFAULT")) != null) {
setAsDefaultButton = device.findObject(UiSelector().text("SET AS DEFAULT"))
setAsDefaultButton?.click()
} else {
clickDialogPositiveButton()
}
}
}
You can use the Pattern object instead of using a string.
You can use in your code like:
val pattern = Pattern.compile("Set as default", Pattern.CASE_INSENSITIVE)
val setDefaultText = device.findObject(UiSelector().text(pattern))
if(setDefaultText != null)) {
setDefaultText.click()
} else {
clickDialogPositiveButton()
}
Based on the Jordan's example and hint, solution to this is to find an object with the Pattern. With pattern, you can search for UIObject with By.text(pattern). Take a note that the object found with the pattern needs to be UIObject2 instead of the UIObject.
val pattern = Pattern.compile("Set as the default", Pattern.CASE_INSENSITIVE)
if(device.findObject(UiSelector().text(pattern.toString())) != null) {
device.findObject(By.text(pattern)).click()
}
I have a button in my activity. Once clicked, it checks user registration status. If registered then shows a dialog otherwise starts registration activity.
I set required flags that says user has registered. I perform click and expect the dialog must be created but after debug I see this is null :(
This is my code:
#Test
public void testSignupButton()
{
PreferenceUtils.setSessionId(activity, "sessionId");
assertTrue(PreferenceUtils.isActivated(activity));
btnSignUp.performClick();
Dialog dialog = ShadowDialog.getLatestDialog(); // << dialog is null
ShadowDialog loginDialogFragment = Shadows.shadowOf(dialog); // Test fails here since dialog is null
assertThat(loginDialogFragment.getTitle().toString(), equalTo("TestDialogFragment"));
}
Any idea, would be appreciated, thanks.
omg, I found where was my problem. I had activity = Robolectric.buildActivity(HitchWelcomeActivity.class).create().get(); in my setupView method which is wrong. In order to get real activity I should have visible() method too.
So, I changed above code to following and my problem fixed.
#Before
public void setUp()
{
activity = Robolectric.buildActivity(WelcomeActivity.class)
.create()
.start()
.resume()
.visible()
.get();
btnSignUp = (Button) activity.findViewById(R.id.dialog_welcome_sign_up);
btnSkip = (TextView) activity.findViewById(R.id.dialog_welcome_next_time);
}