In my test case I have to record for 1 hour, in robotium solo.sleep(600000) had done my work, but In espresso I am confused with IdlingResource concept. I have to start recording and wait for some time(depending on the type of test) 15mins, 60mins etc.
Equivalent code in robotium
solo.clickOnView(solo.getView("start_record"));
solo.sleep(duration * 60 * 1000);
solo.clickOnView(solo.getView("stop_record"));
I tried to use it like this in espresso
#RunWith(AndroidJUnit4.class)
#SmallTest
public class AaEspressoTest {
private static final String LAUNCHER_ACTIVITY_FULL_CLASSNAME = "com.absd.rec.RecorderActivity";
private static Class<?> launcherActivityClass;
private Solo solo;
private static CoreRecordingTest skyroTestRunner;
private static Class<? extends Activity> activityClass;
static {
try {
activityClass = (Class<? extends Activity>) Class.forName(LAUNCHER_ACTIVITY_FULL_CLASSNAME);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
#Rule
public final ActivityTestRule<?> activityRule
= new ActivityTestRule<>(activityClass);
private IntentServiceIdlingResource idlingResource;
#Before
public void registerIntentServiceIdlingResource() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
idlingResource = new IntentServiceIdlingResource(instrumentation.getTargetContext());
Espresso.registerIdlingResources(idlingResource);
}
#After
public void unregisterIntentServiceIdlingResource() {
Espresso.unregisterIdlingResources(idlingResource);
}
#Test
public void testHello() throws Exception {
onView(withId(AaEspressoTest.getId("recorderpage_record"))).perform(click());
registerIntentServiceIdlingResource();
onView(withId(AaEspressoTest.getId("recorderpage_stop"))).perform(click());
}
}
Idling resource
public class IntentServiceIdlingResource implements IdlingResource {
private final Context context;
private ResourceCallback resourceCallback;
public static boolean status = false;
public IntentServiceIdlingResource(Context context) {
this.context = context;
}
#Override
public String getName() {
return IntentServiceIdlingResource.class.getName();
}
#Override
public boolean isIdleNow() {
return getTimer();
}
#Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
private static boolean getTimer() {
new CountDownTimer(5000, 1000) {
#Override
public void onTick(long millisUntilFinished) {
// Do Nothing
status = false;
}
#Override
public void onFinish() {
status = true;
}
};
return status;
}
}
Exception:
android.support.test.espresso.IdlingResourceTimeoutException: Wait for [com.adbs.recorder.IntentServiceIdlingResource] to become idle timed out
You need an IdlingResource with an isIdleNow() that returns true only if the specific amount of time has passed. To achieve that, save the start time and compare it with current time:
public class ElapsedTimeIdlingResource implements IdlingResource {
private final long startTime;
private final long waitingTime;
private ResourceCallback resourceCallback;
public ElapsedTimeIdlingResource(long waitingTime) {
this.startTime = System.currentTimeMillis();
this.waitingTime = waitingTime;
}
#Override
public String getName() {
return ElapsedTimeIdlingResource.class.getName() + ":" + waitingTime;
}
#Override
public boolean isIdleNow() {
long elapsed = System.currentTimeMillis() - startTime;
boolean idle = (elapsed >= waitingTime);
if (idle) {
resourceCallback.onTransitionToIdle();
}
return idle;
}
#Override
public void registerIdleTransitionCallback(
ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
}
Create and register this idling resource in your test:
#Test
public static void waitForOneHour() {
long waitingTime = DateUtils.HOUR_IN_MILLIS;
// Start
onView(withId(AaEspressoTest.getId("recorderpage_record")))
.perform(click());
// Make sure Espresso does not time out
IdlingPolicies.setMasterPolicyTimeout(
waitingTime * 2, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(
waitingTime * 2, TimeUnit.MILLISECONDS);
// Now we wait
IdlingResource idlingResource = new ElapsedTimeIdlingResource(waitingTime);
Espresso.registerIdlingResources(idlingResource);
// Stop
onView(withId(AaEspressoTest.getId("recorderpage_stop")))
.perform(click());
// Clean up
Espresso.unregisterIdlingResources(idlingResource);
}
You need the setMasterPolicyTimeout and setIdlingResourceTimeout calls to make sure Espresso does not terminate the test due to time out.
Full example: https://github.com/chiuki/espresso-samples/tree/master/idling-resource-elapsed-time
The default timeout that Espresso will wait for all registered resources to become idle is one minute.
You can change this using the IdlingPolicies class to set an explicit timeout:
IdlingPolicies.setIdlingResourceTimeout(1, TimeUnit.HOURS);
#Before
public void registerIdlingResource() {
IdlingPolicies.setMasterPolicyTimeout(60 * 1000 * 3, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(60 * 1000 * 3, TimeUnit.MILLISECONDS);
mIdlingResource = BooleanIdlingResource.getIdlingResource();
// To prove that the test fails, omit this call:
IdlingRegistry.getInstance().register(mIdlingResource);
}
I test on my project, It works.
Just setup before register idling resources.
please check:
https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IdlingResourceSample
and
https://developer.android.com/reference/android/support/test/espresso/IdlingPolicies
Related
Here my Espresso's tests:
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import java.util.concurrent.TimeUnit
#RunWith(AndroidJUnit4::class)
class TradersActivityTest {
private lateinit var mockServer: MockWebServer
#Rule
#JvmField
var tradersIntentTestRule = IntentsTestRule(TradersActivity::class.java, false, false)
#Before
fun setup() {
mockServer = MockWebServer()
mockServer.start(8081)
}
#Test
fun hasTraders_noTradersTextView_isNotDisplayed() {
mockServer.enqueue(MockResponse()
.setResponseCode(200)
.setBody(FileUtil.getStringFromFile(context, ONE_TRADER_NO_WALLETS_LIST)));
tradersIntentTestRule.launchActivity(Intent())
onView(withId(R.id.noTradersTextView))
.check(matches(not(isDisplayed())))
}
#Test
fun toolBar_height() {
onView(withId(R.id.toolBar))
.check(matches(withHeightResId(R.dimen.tool_bar_height)))
}
Test hasTraders_noTradersTextView_isNotDisplayed success pass.
But test toolBar_height fail with message:
java.lang.RuntimeException: No activities found. Did you forget to launch the activity by calling getActivity() or startActivitySync or similar?
at androidx.test.espresso.base.RootViewPicker.waitForAtLeastOneActivityToBeResumed(RootViewPicker.java:169)
at androidx.test.espresso.base.RootViewPicker.get(RootViewPicker.java:83)
So I change setup method:
#Before
fun setup() {
mockServer = MockWebServer()
mockServer.start(8081)
mockServer.enqueue(MockResponse()
.setResponseCode(200)
.setBody(FileUtil.getStringFromFile(context, ONE_TRADER_NO_WALLETS_LIST)));
tradersIntentTestRule.launchActivity(Intent())
Debug.d(TAG, "SUCCCESS_START_MOCKWEBSRVER")
}
Now test toolBar_height pass, but hasTraders_noTradersTextView_isNotDisplayed fail with message:
java.lang.RuntimeException: Could not launch intent Intent { flg=0x10000000 com.myproject.android.ui.activity.TradersActivity } within 45 seconds. Perhaps the main thread has not gone idle within a reasonable amount of time? There could be an animation or something constantly repainting the screen. Or the activity is doing network calls on creation? See the threaddump logs. For your reference the last time the event queue was idle before your activity launch request was 1556373134135 and now the last time the queue went idle was: 1556373143180. If these numbers are the same your activity might be hogging the event queue.
at androidx.test.runner.MonitoringInstrumentation.startActivitySync(MonitoringInstrumentation.java:459)
P.S. I need to start activity in test hasTraders_noTradersTextView_isNotDisplayed because it's a specific test.
But I wan't to start activity in test toolBar_height
In my UI automated tests, I have this classes:
public class UiTestHelper {
public static Matcher<View> childAtPosition(
final Matcher<View> parentMatcher, final int position) {
return new TypeSafeMatcher<View>() {
#Override
public void describeTo(Description description) {
description.appendText("Child at position " + position + " in parent ");
parentMatcher.describeTo(description);
}
#Override
public boolean matchesSafely(View view) {
ViewParent parent = view.getParent();
return parent instanceof ViewGroup && parentMatcher.matches(parent)
&& view.equals(((ViewGroup) parent).getChildAt(position));
}
};
}
public static void clickOn(View view) {
waitUntilVisible(view);
onView(withId(view.getId())).perform(click());
}
public static void waitUntilVisible(View view) {
ViewVisibilityIdlingResource idlingResource = new ViewVisibilityIdlingResource(view, VISIBLE);
IdlingRegistry.getInstance().register(idlingResource);
}
public static void unregisterAllIdlingResources() {
IdlingRegistry idlingRegistry = IdlingRegistry.getInstance();
for (IdlingResource resource: idlingRegistry.getResources()) {
idlingRegistry.unregister(resource);
}
}
}
and
public class ViewVisibilityIdlingResource implements IdlingResource {
private final View view;
private final int expectedVisibility;
private boolean idle;
private ResourceCallback resourceCallback;
public ViewVisibilityIdlingResource(final View view, final int expectedVisibility) {
this.view = view;
this.expectedVisibility = expectedVisibility;
this.idle = false;
this.resourceCallback = null;
}
#Override
public final String getName() {
return ViewVisibilityIdlingResource.class.getSimpleName();
}
#Override
public final boolean isIdleNow() {
idle = idle || (view.getVisibility() == expectedVisibility && view.getWidth() > 0 && view.getHeight() > 0);
if (idle) {
if (resourceCallback != null) {
resourceCallback.onTransitionToIdle();
}
}
return idle;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
}
Then when I want to check that a view is there I call
UiTestHelper.waitUntilVisible(view);
I'm doing banking application after 5 mins activity should go to the login screen, I referred many links and implemented,
It's working when the app is in the foreground, I'm calling timer and checking after 5 mins I'm redirected to login screen.
But when the app is in the background how to handle, if I login same timer and redirected to login page means automatically its opened.it should not open right.
In background itself, it should move to login screen right. Please help me to solve my problem.
I referred this link:https://gist.github.com/dseerapu/b768728b3b4ccf282c7806a3745d0347
One of mine application is successfully working on auto logout after 5 minutes whether app is in background or foreground state.
To create auto logout for 5 minutes first create all class as below:
ApplockManager
public class ApplockManager {
private static ApplockManager instance;
private DefaultApplock currentAppLocker;
public static ApplockManager getInstance() {
if (instance == null) {
instance = new ApplockManager();
}
return instance;
}
public void enableDefaultAppLockIfAvailable(Application currentApp) {
currentAppLocker = new DefaultApplock(currentApp);
}
public void startWaitThread(Context context){
currentAppLocker.startWaitThread(context);
}
public void updateTouch(){
currentAppLocker.updateTouch();
}
public void setStopTrue(){
currentAppLocker.setStopTrue();
}
public void setStopFalse(){
currentAppLocker.setStopFalse();
}
}
DefaultApplock
public class DefaultApplock implements Application.ActivityLifecycleCallbacks {
final String TAG = DefaultApplock.class.getSimpleName();
private Application mCurrentApp;
private long WAIT_TIME = 5 * 60 * 1000;
private Waiter waiter;
private Date mLostFocusDate;
public DefaultApplock(Application app) {
super();
mCurrentApp = app;
//Registering Activity lifecycle callbacks
mCurrentApp.unregisterActivityLifecycleCallbacks(this);
mCurrentApp.registerActivityLifecycleCallbacks(this);
}
#Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
#Override
public void onActivityStarted(Activity activity) {
}
#Override
public void onActivityResumed(Activity activity) {
// for UserInactivity
// for Screen lock
if (shouldShowUnlockScreen()) {
Log.d(TAG, "time over");
Intent intent = new Intent(activity.getApplicationContext(), SplashActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
Log.d(TAG, "changing mLostFocus to null");
mLostFocusDate = null;
activity.getApplicationContext().startActivity(intent);
}
}
private boolean shouldShowUnlockScreen() {
Boolean isvalid = false;
if (mLostFocusDate == null) {
isvalid = false;
} else {
Log.d(TAG, "Timeout ->"+timeSinceLocked());
if (timeSinceLocked() >= (WAIT_TIME/1000)) {
isvalid = true;
} else {
mLostFocusDate = null;
}
}
Log.d(TAG, isvalid.toString());
return isvalid;
}
private int timeSinceLocked() {
return Math.abs((int) ((new Date().getTime() - mLostFocusDate.getTime()) / 1000));
}
#Override
public void onActivityPaused(Activity activity) {
/*if(waiter!=null) {
waiter.stopThread();
}*/
mLostFocusDate = new Date();
}
#Override
public void onActivityStopped(Activity activity) {
}
#Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
#Override
public void onActivityDestroyed(Activity activity) {
}
public void startWaitThread(Context context){
/*if(waiter!=null) {
waiter.stopThread();
}*/
waiter = new Waiter(context, WAIT_TIME);
waiter.start();
}
public void updateTouch() {
if(waiter!=null) {
waiter.touch();
}
mLostFocusDate = new Date();
}
public void setStopTrue() {
if(waiter!=null) {
waiter.setStopTrue();
}
}
public void setStopFalse() {
if(waiter!=null) {
waiter.setStopFalse();
}
}
}
Waiter
public class Waiter extends Thread
{
private static final String TAG=Waiter.class.getName();
private long lastUsed;
private long period;
private boolean stop = false;
private Context mContext;
SessionManager session;
public Waiter(Context context,long period) {
this.period=period;
stop=false;
mContext = context;
session = new SessionManager(context.getApplicationContext());
}
public void run() {
long idle=0;
this.touch();
do
{
idle = System.currentTimeMillis() - lastUsed;
if(idle > period)
{
idle=0;
// Perform Your desired Function like Logout or expire the session for the app.
stopThread();
}
}
while(!stop);
Log.d(TAG, "Finishing Waiter thread");
}
public synchronized void touch() {
lastUsed = System.currentTimeMillis();
}
public synchronized void setStopTrue() {
stop = true;
}
public synchronized void setStopFalse() {
stop = false;
}
public synchronized void forceInterrupt() {
this.interrupt();
}
public synchronized void setPeriod(long period)
{
this.period=period;
}
public synchronized void stopThread() {
stop = true;
session.logoutUserInBackgroundOrForeground(mContext);
}
public synchronized void startThread() {
stop = false;
}
}
SessionManager
public class SessionManager {
// Shared Preferences
SharedPreferences pref;
// Editor for Shared preferences
Editor editor;
// Context
Context _context;
// Shared pref mode
int PRIVATE_MODE = 0;
// Sharedpref file name
private static final String PREF_NAME = "MyAutoLogoutAppPref";
// All Shared Preferences Keys
private static final String IS_LOGIN = "IsLoggedIn";
// Constructor
public SessionManager(Context context) {
this._context = context;
pref = _context.getSharedPreferences(PREF_NAME, PRIVATE_MODE);
editor = pref.edit();
}
/**
* Create login session
*/
public void createLoginSession() {
// Storing login value as TRUE
editor.putBoolean(IS_LOGIN, true);
// commit changes
editor.commit();
}
public void setIsLogin(boolean login) {
editor.putBoolean(IS_LOGIN, login);
editor.commit();
}
public boolean isLoggedIn() {
return pref.getBoolean(IS_LOGIN, false);
}
public void logoutUserInBackgroundOrForeground(Context context) {
setIsLogin(false);
editor.clear();
editor.commit();
Intent i = new Intent(_context, LoginActivity.class);
// Closing all the Activities
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// Add new Flag to start new Activity
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// Staring Login Activity
_context.startActivity(i);
}
}
MyAutoLogoutApp
public class MyAutoLogoutApp extends Application {
public static MyAutoLogoutApp myAutoLogoutApp;
#Override
public void onCreate() {
super.onCreate();
myAutoLogoutApp = this;
ApplockManager.getInstance().enableDefaultAppLockIfAvailable(this);
ApplockManager.getInstance().startWaitThread(myAutoLogoutApp);
}
public void touch() {
ApplockManager.getInstance().updateTouch();
}
public void setStopTrue() {
ApplockManager.getInstance().setStopTrue();
}
public void setStopFalse() {
ApplockManager.getInstance().setStopFalse();
ApplockManager.getInstance().startWaitThread(MyAutoLogoutApp.myAutoLogoutApp);
}
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
BaseActivity
public class BaseActivity extends AppCompatActivity implements View.OnClickListener {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onUserInteraction() {
super.onUserInteraction();
MyAutoLogoutApp.myAutoLogoutApp.touch();
}
}
Finally your every activity must extends BaseActivity so when user touches any where then onUserInteraction() method will fire and timer will reset and it will work for both background and foreground scenario.
I am creating Android application using MVP pattern.
For that I am using Retrofit 2 and RxJava. App works fine
But in unit testing I am getting weird error.Same test code sometimes passes, sometimes fails.
Error displays with this message
Wanted but not invoked:
albumView.showProgress();
-> at kz.afckairat.kairat.media.AlbumPresenterTest.checkGetPhotoAlbums(AlbumPresenterTest.java:66)
Actually, there were zero interactions with this mock.
Test class
public class AlbumPresenterTest {
enter code here
private MediaService mediaService;
private AlbumView albumView;
private AlbumPresenterImpl photoAlbumPresenter;
#Before
public void setUp() throws Exception {
albumView = mock(AlbumView.class);
mediaService = mock(MediaService.class);
photoAlbumPresenter = new AlbumPresenterImpl(albumView, mediaService, MediaType.PHOTO);
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
}
#After
public void tearDown() {
RxAndroidPlugins.getInstance().reset();
}
#Test
public void checkGetPhotoAlbums() {
List<Album> albums = getAlbumList();
when(mediaService.getPhotoAlbums()).thenReturn(Observable.just(albums));
photoAlbumPresenter.getAlbums();
verify(albumView).showProgress();
verify(albumView).showAlbums(albums);
verify(albumView).hideProgress();
}
#Test
public void checkGetPhotoAlbumError() {
String msg = "Error";
when(mediaService.getPhotoAlbums()).thenReturn(Observable.error(new IOException(msg)));
photoAlbumPresenter.getAlbums();
verify(albumView).showProgress();
verify(albumView).showError(msg);
verify(albumView).hideProgress();
}
private List<Album> getAlbumList() {
List<Album> albums = new ArrayList<>();
Album album = new Album(1, "Test1", "test1.jpg", "01.01.2016", 2);
albums.add(album);
album = new Album(2, "Test2", "test2.jpg", "01.01.2016", 2);
albums.add(album);
return albums;
}
}
Presenter class which is tested
public class AlbumPresenterImpl implements AlbumPresenter {
private AlbumView view;
private MediaType type;
private List<Album> albums;
private MediaService mediaService;
public AlbumPresenterImpl(AlbumView view, MediaService mediaService, MediaType type) {
this.view = view;
this.mediaService = mediaService;
this.type = type;
}
#Override
public void getAlbums() {
Observable<List<Album>> observable;
if (type.equals(MediaType.VIDEO)) {
observable = mediaService.getVideoAlbums();
} else {
observable = mediaService.getPhotoAlbums();
}
observable.doOnSubscribe(view::showProgress)
.doAfterTerminate(view::hideProgress)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(items -> {
albums = items;
view.showAlbums(albums);
}, throwable -> {
view.showError(throwable.getLocalizedMessage());
});
}
#Override
public void onResume() {
if (albums == null) {
getAlbums();
}
}
#Override
public void onDestroy() {
}
}
Why sometimes test don't pass?
Thanks a lot!
=================================
Update
As #Fred wrote problem was in Schedulers
public class RxSchedulersOverrideRule implements TestRule {
private final RxJavaSchedulersHook mRxJavaSchedulersHook = new RxJavaSchedulersHook() {
#Override
public Scheduler getIOScheduler() {
return Schedulers.immediate();
}
#Override
public Scheduler getNewThreadScheduler() {
return Schedulers.immediate();
}
};
private final RxAndroidSchedulersHook mRxAndroidSchedulersHook = new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
};
// Hack to get around RxJavaPlugins.reset() not being public
// See https://github.com/ReactiveX/RxJava/issues/2297
// Hopefully the method will be public in new releases of RxAndroid and we can remove the hack.
private void callResetViaReflectionIn(RxJavaPlugins rxJavaPlugins)
throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Method method = rxJavaPlugins.getClass().getDeclaredMethod("reset");
method.setAccessible(true);
method.invoke(rxJavaPlugins);
}
#Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
RxAndroidPlugins.getInstance().reset();
RxAndroidPlugins.getInstance().registerSchedulersHook(mRxAndroidSchedulersHook);
callResetViaReflectionIn(RxJavaPlugins.getInstance());
RxJavaPlugins.getInstance().registerSchedulersHook(mRxJavaSchedulersHook);
base.evaluate();
RxAndroidPlugins.getInstance().reset();
callResetViaReflectionIn(RxJavaPlugins.getInstance());
}
};
}
}
Code taken from Github a link!
And in Test class
#Rule
public final RxSchedulersOverrideRule mOverrideSchedulersRule = new RxSchedulersOverrideRule();
It seems you override the main thread scheduler with:
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
But from the code, the observables still run on the Schedulers.io() scheduler:
observable.doOnSubscribe(view::showProgress)
.doAfterTerminate(view::hideProgress)
.subscribeOn(Schedulers.io())
// ...
As you may know, the immediate scheduler executes code in the current thread, which I guess since you jump to the io scheduler it's a different one from the one the tests run on.
This will make the test run in one thread and the subscribers/observables in another. This would explain why sometimes the tests pass and sometimes they don't. There's a race condition.
Essential the easiest way is to make sure that at test time you have both observeOn and subscribeOn on Schedulers.immediate() and at run time you have the correct ones, i.e., Schedulers.io() and AndroidSchedulers.mainThread().
You can do this by overriding the schedulers, by passing them as constructors or you could even take a look at this where Dan Lew explains how to use compose to create scheduler transformers. You can then make sure your classes at run time use a proper scheduler transformer and at test time they use some transformer that puts everything on the immediate thread.
I recently converted my application from using async tasks to rxjava. Now, my espresso tests no longer wait for my data calls to complete due to espresso not having an idling resources for rxjava. I noticed that you can make custom idling resources but I can't figure out how to make it work with rxJava Schedulers, Scheduler.io specifically. Any help/best practice would be greatly appreciated.
Here is how I solved the problem:
IdlingResource implementation:
public class IdlingApiServiceWrapper implements MyRestService, IdlingResource {
private final MyRestService api;
private final AtomicInteger counter;
private final List<ResourceCallback> callbacks;
public IdlingApiServiceWrapper(MyRestService api) {
this.api = api;
this.callbacks = new ArrayList<>();
this.counter = new AtomicInteger(0);
}
public Observable<MyData> loadData(){
counter.incrementAndGet();
return api.loadData().finallyDo(new Action0() {
#Override
public void call() {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
counter.decrementAndGet();
notifyIdle();
}
});
}
});
}
#Override public String getName() {
return this.getClass().getName();
}
#Override public boolean isIdleNow() {
return counter.get() == 0;
}
#Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
callbacks.add(resourceCallback);
}
private void notifyIdle() {
if (counter.get() == 0) {
for (ResourceCallback cb : callbacks) {
cb.onTransitionToIdle();
}
}
}
}
and here is my test:
public class MyActivityTest extends ActivityInstrumentationTestCase2<MyActivity> {
#Inject
IdlingApiServiceWrapper idlingApiWrapper;
#Override
public void setUp() throws Exception {
//object graph creation
super.setUp();
getActivity();
Espresso.registerIdlingResources(idlingApiWrapper);
}
public void testClickOpenFirstSavedOffer() throws Exception {
onData(is(instanceOf(DataItem.class)))
.atPosition(0)
.perform(click());
}
}
I used Dagger for dependency injection.
Wrote a little integration piece between RxJava Plugins and Espresso. Hope this helps someone else.
https://gist.github.com/digitalbuddha/d886eae1578bca78b9bf
Edit:
There is a much easier way to accomplish this task. Add the following rule to your tests
public class AsyncTaskSchedulerRule implements TestRule {
final Scheduler asyncTaskScheduler = Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR);
#Override
public Statement apply(Statement base, Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
RxJavaHooks.setOnIOScheduler(scheduler -> asyncTaskScheduler);
RxJavaHooks.setOnComputationScheduler(scheduler -> asyncTaskScheduler);
RxJavaHooks.setOnNewThreadScheduler(scheduler -> asyncTaskScheduler);
try {
base.evaluate();
} finally {
RxJavaHooks.reset();
}
}
};
}
}
I am currently using this implementation. Its easier and works very well for me so far: https://github.com/rosshambrick/RxEspresso
I have a firstActivity that launches the secondActivity, where in the secondActivity I have a loading Dialog (not AsyncTask), and I need to make Espresso wait until the dialog disappears before it continues with the test.
Where do I have to implement the IdlingResource? How can I make it wait for the dismissDialog() function?
Here is what I've tried to do:
class DocumentLoadingIdlingResource implements IdlingResource {
private ResourceCallback callback;
#Override
public String getName() {
return "Documnet loading idling resource";
}
#Override
public boolean isIdleNow() {
Activity activity;
try {
activity = getCurrentActivity();
} catch (Throwable e) {
return false;
}
if(activity.getClass().getName().equals(EditorActivity.class.getName())
&& activity.loadingDialogShowing() == false) {
return false;
}
return true;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
this.callback = callback;
}
}
Activity getCurrentActivity() throws Throwable {
getInstrumentation().waitForIdleSync();
final Activity[] activity = new Activity[1];
runTestOnUiThread(new Runnable() {
#Override
public void run() {
java.util.Collection<Activity> activites = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);
activity[0] = com.google.common.collect.Iterables.getOnlyElement(activites);
}});
return activity[0];
}
This class is implemented in the test class.
There are a few problems here:
Your isIdleNow() calls getCurrentActivity() which calls waitForIdleSync() and runTestOnUiThread(). isIdleNow Javadoc says: "Espresso will always call this method from the main thread, therefore it should be non-blocking and return immediately." So this won't work as is, but you could call getActivitiesInStage directly from isIdleNow.
Your other issue is that you store the reference to ResourceCallback but never invoke onTransitionToIdle, also you should allow for the possibility of more than one ResourceCallback being registered and call onTransitionToIdle on all of the callbacks.
You can do the following:
Copy/Paste IdlingResource into your app as com.mycompany.IdlingResource.
Then have your Activity implement that interface and make sure to call onTransitionToIdle when the dialog goes away and make sure isIdleNow returns false iff the dialog is showing.
In your test code, write a "IdlingResourceAdapter" that wraps com.mycompany.IdlingResource and turns it into an Espresso IdlingResource and register that with Espresso.
This will be simpler once this issue is implemented: https://code.google.com/p/android-test-kit/issues/detail?id=71
I stumbled upon this question in my search for a similar answer. Using concepts from Stefano Dacchille's article on IdlingResources, I built the following idling resource that waits for a specific Activity to be active before firing. In my case, I know the dialog is showing when a fragment with a specific tag exists. This isn't the same as the OP's test, but the concepts should translate well.
public class BusyWhenFragmentExistsInActivityIdlingResource implements IdlingResource {
private FragmentActivity activity = null;
private final String fragmentTag;
private ResourceCallback resourceCallback;
private boolean wasIdleLastTime = true; // Start off as idle
private final String name;
// Need this strong reference because ActivityLifecycleMonitorRegistry won't hold one
private final ActivityLifecycleCallback activityLifecycleCallback;
public BusyWhenFragmentExistsInActivityIdlingResource(
final Class<? extends FragmentActivity> clazz,
final String fragmentTag
){
name = BusyWhenFragmentExistsInActivityIdlingResource.class.getSimpleName()+" "+clazz.getSimpleName();
this.fragmentTag = fragmentTag;
activityLifecycleCallback = new ActivityLifecycleCallback() {
#Override
public void onActivityLifecycleChanged(Activity activity, Stage stage) {
if (!FragmentActivity.class.isAssignableFrom(activity.getClass())) {
return;
}
FragmentActivity fragmentActivity = (FragmentActivity) activity;
if (!clazz.isAssignableFrom(fragmentActivity.getClass())) {
return;
}
switch (stage){
case RESUMED:
BusyWhenFragmentExistsInActivityIdlingResource.this.activity = fragmentActivity;
break;
case STOPPED:
BusyWhenFragmentExistsInActivityIdlingResource.this.activity = null;
break;
}
}
};
ActivityLifecycleMonitorRegistry.getInstance()
.addLifecycleCallback(activityLifecycleCallback);
}
#Override
public String getName() {
return name;
}
#Override
public boolean isIdleNow() {
if (activity==null) {
return wasIdleLastTime = true;
}
boolean isIdleThisTime = activity
.getSupportFragmentManager()
.findFragmentByTag(fragmentTag)==null;
if (!wasIdleLastTime && isIdleThisTime && resourceCallback!=null){
resourceCallback.onTransitionToIdle();
}
return wasIdleLastTime = isIdleThisTime;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
}
To use it, add something similar to this to your test:
#Before
public void setUp() throws Exception {
registerIdlingResources(new BusyWhenFragmentExistsInActivityIdlingResource(
SomeOtherActivity.class,
BaseActivity.LOADING_DIALOG
));
}