Espresso Idling Resource doesn't work - android

The Android espresso is very useful for test case. But when I use IdlingResource there is some trouble.
I have a flag in my Activity, and I will set it to true when every initial complete.
So my IdlingResource is this:
/**
* 等待所有初始化工作完成
*/
private class WaitPingSuccessIdlingResource implements IdlingResource {
private ChoiceServerActivity choiceServerActivity;
private ResourceCallback mResourceCallback;
public WaitPingSuccessIdlingResource(ChoiceServerActivity choiceServerActivity) {
this.choiceServerActivity = choiceServerActivity;
}
#Override
public String getName() {
return String.valueOf(hashCode());
}
#Override
public boolean isIdleNow() {
if (mResourceCallback != null && choiceServerActivity.isAllDataInited()) {
mResourceCallback.onTransitionToIdle();
}
boolean rst = choiceServerActivity.isAllDataInited();
Log.i("tonghu","WaitPingSuccessIdlingResource, isIdleNow(L94): rst " + rst);
return rst;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
this.mResourceCallback = callback;
}
}
And I register like this:
Espresso.registerIdlingResources(new WaitPingSuccessIdlingResource(activity));
Log.i("tonghu", "ChoiceServerActivityTest, testPingSuccess(L42): 2222");
In normally, the second log will print only when isIdleNow() return true.
But now my log is:
I/tonghu (23470): WaitPingSuccessIdlingResource, isIdleNow(L94): rst false
I/tonghu (23470): ChoiceServerActivityTest, testPingSuccess(L42): 2222
Why the second log can print when my IdlingResource wasn't idle.
My English is poor, any problem, please let me know! Thx!
EDITED:
I have already solve this problem:
I see there is a comment on class IdlingResource:
In such cases, test authors can register the custom resource and
{#link Espresso} will wait for the resource to become idle prior
to executing a view operation.
So after register Idling resource, just give any a view action:
Espresso.registerIdlingResources(new WaitPingSuccessIdlingResource(activity));
Espresso.onView(ViewMatchers.withId(R.id.list_view)).check(ViewAssertions.matches(ViewMatchers.isDisplayed()));

Same problem here, found that registering idlingResources won't cause Espresso to wait, but besides Espresso.onView, you still can use Espresso.onIdle() to wait for registered idlingResources to turn idle.
Finally, I found the official document, quote from here:
Register idling resources before you need them.
The synchronization benefits associated with idling resources only take effect following Espresso's first invocation of that resource's
isIdleNow() method.
The following list shows several examples of this property:
If you register an idling resource in a method annotated with
#Before, the idling resource takes effect in the first line of each
test.
If you register an idling resource inside a test, the idling resource takes effect during the next Espresso-based action. This
behavior still occurs even if the next action is in the same test as
the statement that registers the idling resource.

Related

Testing progress bar on Android with Espresso

The workflow should be the following:
Activity starts
Progress bar is visible
Network request fires (idling resource is already registered so espresso knows how to wait for it).
Progress bar is hidden
Text from network is shown.
Up to this point, I have written assertions for steps 1, 3, 5 and it works perfectly:
onView(withText("foo 1"))
.check(matches(isDisplayed()));
Problem is, I have no idea how to let espresso know to verify the visibility of progress bar before the request is made and after the request is made.
Consider the onCreate() method is the following:
super.onCreate(...);
setContentView(...);
showProgressBar(true);
apiClient.getStuff(new Callback() {
public void onSuccess() {
showProgressBar(false);
}
});
I have tried the following but it doesn't work:
// Activity is launched at this point.
activityRule.launchActivity(new Intent());
// Up to this point, the request has been fired and response was
// returned, so the progress bar is now GONE.
onView(withId(R.id.progress_bar))
.check(matches(isDisplayed()));
onView(withId(R.id.progress_bar))
.check(matches(not(isDisplayed())));
The reason this is happening is because, since the client is registered as an idling resource, espresso will wait until it is idle again before running the first onView(...progressbar...)... so I need a way to let espresso know to run that BEFORE going to idle.
EDIT: this doesn't work either:
idlingResource.registerIdleTransitionCallback(new IdlingResource.ResourceCallback() {
#Override
public void onTransitionToIdle() {
onView(withId(R.id.progress_bar))
.check(matches(isDisplayed()));
}
});
Espresso has problems with the animation. You can just set the drawable of the progress bar to something static just for the test and it works as expected.
Drawable notAnimatedDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.whatever);
((ProgressBar) getActivity().findViewById(R.id.progress_bar)).setIndeterminateDrawable(notAnimatedDrawable);
onView(withId(R.id.progress_bar)).check(matches(isDisplayed()));
As I can see Espresso is tightly coupled with skipping dynamic UI actions whatsoever, that's why you can't test ProgressBar using Espresso. However, you can easily accomplish this with another Android Google tool: UiAutomator as following:
saveButton().click(); // perform action opening ProgressBar with UiAutomator, not Espresso
assertTrue(progressBar().exists());
Using these static utils:
public static UiObject progressBar() {
return uiObjectWithText(R.string.my_progress);
}
public static UiObject saveButton() {
return uiObjectWithId(R.id.my_save_button);
}
public static UiObject uiObjectWithId(#IdRes int id) {
String resourceId = getTargetContext().getResources().getResourceName(id);
UiSelector selector = new UiSelector().resourceId(resourceId);
return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}
public static UiObject uiObjectWithText(#StringRes int stringRes) {
UiSelector selector = new UiSelector().text(getTargetContext().getString(stringRes));
return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}
Make sure your build.gradle includes:
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
It looks like this may not be truly possible. Though it is an older group posting, there is a fairly decisive answer in the Android Test Kit Discussion where it is stated that the UI threads don't rest during the animation of progress bars, and so the Espresso framework cannot execute.
Marcus Klepp recommends moving past this here through the use of build types. The Gradle plugin will permit you to define different build types. You could set up a different layout in your androidTest build type which replaces the View in question with something generic. If all you're doing is confirming that the widget isDisplayed() under one set of conditions, and not(isDisplayed()) under another set of conditions, then you could surely implement that through different layout files. Not that it is not a little bit of a lift.
Finally, there may be another post here which carries some additional information here: "java.lang.RuntimeException: Could not launch intent" for UI with indeterminate ProgressBar
In my case solution which was provided above works as well but I simplify it, so added build.gradle uiautomator library
androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
and created a new class which will work for example with Progress bar
public class ProgressBarHandler {
public static void waitUntilGoneProgressBar() {
progressBar().waitUntilGone(10000);
}
private static UiObject progressBar() {
return uiObjectWithId(R.id.progress_bar);
}
private static UiObject uiObjectWithId(#IdRes int id) {
String resourceId = getTargetContext().getResources().getResourceName(id);
UiSelector selector = new UiSelector().resourceId(resourceId);
return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}
}
and in my tests use all Espresso methods and when needed only then address to UiAutomator in tests, for example
public class LoginTest extends AbstractTest {
#Rule
public ActivityTestRule<LoginActivity> createAccountActivityTestRule = new ActivityTestRule<>(LoginActivity.class);
#Test
public void loginTest() {
onView(withId(R.id.login_email)).perform(typeText("autotest666#gmail.com"));
onView(withId(R.id.input_password)).perform(typeText("Password123."));
onView(withId(R.id.login_log_in)).perform(click());
waitUntilGoneProgressBar();
onView(withId(R.id.fragment_home_title)).check(matches(isDisplayed()));
}

Check whether an activity has finished loading all data without changing application code

I am trying to find a way to verify that an activity has finished loading everything only without doing any changes in the application code. The method mentioned in this question and many others requires some application code change and I would like to do it via androidTest section.
There are scenarios when the activity is not fully loaded and running the following code fails:
onView(allOf(withId(R.id.user_name), withText("username1"))).perform(click());
In this example I am waiting for a ListView to load, so the data may also be loaded asynchronously (I am using espresso).
Might be too late but you should take a look at espresso idling resource to sync your background loading tasks with espresso. You wont need to change any of your application code. Here you have a deeper insight on android custom idling resources: http://dev.jimdo.com/2014/05/09/wait-for-it-a-deep-dive-into-espresso-s-idling-resources/
or this http://blog.sqisland.com/2015/04/espresso-custom-idling-resource.html
Here is what I did to make espresso wait for my list to be populated (from data comming from the network) before running the UI test.
public class ListResultsIdlingResource implements IdlingResource {
private ResourceCallback callback;
private RecyclerView mRecyclerListings;
public ListResultsIdlingResource(RecyclerView recyclerListings) {
mRecyclerListings = recyclerListings;
}
#Override
public boolean isIdleNow() {
if (mRecyclerListings != null && mRecyclerListings.getAdapter().getItemCount() > 0) {
if (callback != null) {
callback.onTransitionToIdle();
}
return true;
}
return false;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
this.callback = callback;
}
#Override
public String getName() {
return "Recycler idling resource";
}
You just have to check that your list has items in you isIdleNow() method before running your UI test over its elements.
And in your espresso test class in your setup method register your idling resource and pass it a reference to your ListView or Recyclerview or any view you are using as list.
#Before
public void setUp() throws Exception {
Intent intent = createIntentForActivity();
activityRule.launchActivity(intent);
mActivity = activityRule.getActivity();
mListResultsIdlingResource = new ListingResultsIdlingResource(
(RecyclerView) mActivity.findViewById(R.id.recycler_view));
registerIdlingResources(mListResultsIdlingResource);
}
Hope this is helpful for anyone looking for this.

Correct way to use IdlingResource in Espresso Android

I'm writing UI tests with Espresso. App cooperates tightly with server, so in many cases, I need to wait for either value to be calculated, or data is got and displayed, etc. Espresso suggests using IdlingResource for this.
My IdlingResource classes look like this (simple and clear example).
public class IRViewVisible implements IdlingResource {
private View view;
private ResourceCallback callback;
public IRViewVisible(View view) {
this.view = view;
}
#Override
public String getName() {
return IRViewVisible.class.getName();
}
#Override
public boolean isIdleNow() {
if(view.getVisibility() == View.VISIBLE && callback != null) {
callback.onTransitionToIdle();
return true;
}
return false;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.callback = resourceCallback;
}
}
Please correct me if I'm wrong anywhere (as sometimes it seems to me that my IdlingResources do not work properly).
I register the idling resource in setUp() like this:
IRViewVisible ir = new IRViewVisible(View v);
Espresso.registerIdlingResources(ir).
Unregister it on tearDown().
I found this article (there is a section called "Register a component tied to an Activity instance") — I do not use his schema, but I checked hashcode of view that was set to IdlingResource after registering (in each method), and it's not the same view — all hashes are different.
Another question: One Test class (it's results) can't have any effect on another Test class, can it?
I'm guessing your problem stems from getName() returning the same name for all instances of IRViewVisible. This means you can only have one registered instance of it at a time - any subsequent registrations will fail (silently!).
You mention that you clear the IdlingResources at the end of each test, but if you are register multiple instances of it at once, you need to make sure each instance has a unique name. it's not clear from your question if you're registering multiple instances of IRViewVisible in a single test.
As to your final question: Yes, it is possible. Android doesn't completely shut down the Application between test runs - just the Activities. Common things which can cause problems:
Failing to clear persistent state (saved data).
Failing to clear global state - e.g. static variables/singletons
Not waiting for background threads to finish running.
As an aside, it's worth noting that you only call onTransitionToIdle() inside isIdleNow(). This works (thanks #Be_Negative for the heads up!) but it could slow down your tests a lot, since Espresso will only poll isIdleNow() every few seconds. If you call onTransitionToIdle() as soon as the view becomes visible, it should speed things up considerably.
I needed something similar to your IRViewVisible myself, here's my effort.
So the isIdleNow() method will never return true if you don't set a callback to the idlingResource?
I reckon it's better to refactor it like this:
#Override
public boolean isIdleNow() {
boolean idle = view.getVisibility() == View.VISIBLE;
if(idle && callback != null) {
callback.onTransitionToIdle();
}
return idle;
}
Well, first of all you shouldn't need to use Espresso IdlingResource to test server calls. If you use AsyncTasks in your server calls, Espresso will be able to know when to be idle and when not. If this is not enough: try to refactor your code in this way:
IRViewVisible idlingResource = new IRViewVisible(yourView);
IdlingPolicies.setMasterPolicyTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
// Now we wait
Espresso.registerIdlingResources(idlingResource);
// Stop and verify
// Clean up
Espresso.unregisterIdlingResources(idlingResource);
Hope to be helpful.

Android Service communcation

I'm writing an application which run a background Service which communicate with a remote server.
when the server sends me a new message, i need to update an object which is represent in the UI and then to update the UI View to represent the new state of the object (for example if the object's background propery is true - set the background of the View to green and if false set the background of the view to red).
I'm using a list view to show all an ArrayList of all those objects throw an ArrayAdapter.
I have an Application object (named app) for static reference and i have there a CurrentActivity property which store the current activity running (or null if the UI is closed).
i'm using this code to update the UI:
in my Service:
onNewMessage(boolean backgruond)
{
if (app.getCurrentActivity != null)
app.getCurrentActivity.onNewMessage(background);
}
in my Activity:
onNewMessage(boolean background)
{
object.setBackground(bacground);
Log.d("Background", String.valueof(background));
runOnUiThread(new Runnable() {
#Override
public void run()
{
arrayAdapter.notifyDataSetChanged();
}
});
}
and although the Log returns the right background state, the view isn't refreshing with the notifyDataSetChanged();
i've tried to send message to Activity throw BroadcastRecevier but it much more complicated because i have lots of messages coming from the server and i will have to register many receivers.
And besides - i don't understand why would the recevier work and this mechanism wont..
example of working method which updates the ListView:
ListViewActivity - inheritance from BaseActivity:
#Override
public void onUnFriend(FacebookUser facebookUser, boolean isYouRemovedClient)
{
super.onUnFriend(facebookUser, isYouRemovedClient);
updateView();
}
BaseActivity (the super class which extends Activity):
public void onUnFriend(FacebookUser facebookUser, boolean isYouRemovedClient)
{
facebookUser.setApplicationFriend(false);
app.getApplicationFriends().remove(facebookUser);
app.getDatabaseManager().deleteApplicationFriend(facebookUser.getId());
if (isYouRemovedClient)
app.showToast(facebookUser.getName() + " has removed from your friends", true);
else
app.showToast(facebookUser.getName() + " has removed you from friends", true);
}
this one works and does change the background color in the ListView.
not working example
ListViewActivity:
#Override
public void onFriendRequestAccepted(FacebookUser facebookUser, boolean showDialog) {
super.onFriendRequestAccepted(facebookUser, showDialog);
updateView();
}
BaseActivity:
public void onFriendRequestAccepted(FacebookUser facebookUser, boolean showDialog)
{
facebookUser.setApplicationFriend(true);
app.getApplicationFriends().add(facebookUser);
app.getDatabaseManager().addApplicationFriend(facebookUser);
if (showDialog)
app.showNewEventActivity(facebookUser, EventDialogManager.EVENT_FRIEND_ACCEPTED);
}
no update is made... i can't really understand why..
i have there a CurrentActivity property which store the current activity running (or null if the UI is closed)
I do not recommend this practice. It relies upon you consistently and reliably updating that Application data member, and it increases the coupling between your service and your UI.
and although the Log returns the right background state, the view isn't refreshing with the notifyDataSetChanged();
It would appear that you did not change the data in the adapter. Certainly, there is no evidence in the code that you have here that you updated the data in the adapter.
BTW, neither of the code snippets you have shown here are likely to compile (first is not valid Java, second has a typo).
i have lots of messages coming from the server and i will have to register many receivers
No, you will have to register one receiver, and in onReceive(), use an if statement (or perhaps a switch, if you prefer) to distinguish one message from another.
In addition to what CommonsWare said, I assume that object in the first line of your onNewMessage is the view. setBackround accepts an int parameter, not a boolean.
Use 0xFF00FF00 for green and 0xFFFF0000 for red.
By the way, it's a very bad practice to keep static references of Context objects and it's derived classes (Application and Activity both derive from Context, and keeping a static reference of them may lead to serious memory leaks. Read more here.)
Use a BroadcastReceiver instead. They are much more simple comparing to how you described them - you only need one.

Android: How to determine waiting threads?

Want to create a unit test for android library project. I need to test if the given thread is waiting (synchronized object.wait() was called) or not. Is it possible to determine it?
A common way to write unit tests to see if a method is called is to create a mock object that overrides the method to check and sets a flag when it is called. For example:
public class MockYourClass extends YourClass {
public boolean mWaitWasCalled = false;
#Override
public void wait() {
mWaitWasCalled = true;
super.wait();
}
}
Substitute the use of your class for this mock and then check that assertTrue(mockClass.mWaitWasCalled)
Given a random object, there's no way to tell if a thread is waiting on it.

Categories

Resources