Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Espresso not waiting till Activity is destroyed, before creating a new one for the next test

The Situation

In the official documentation here: https://google.github.io/android-testing-support-library/docs/rules/index.html, it says:

"This rule provides functional testing of a single activity. The activity under test will be launched before each test annotated with @Test and before any method annotated with @Before. It will be terminated after the test is completed and all methods annotated with @After are finished. The Activity under Test can be accessed during your test by calling ActivityTestRule#getActivity()."

Technically yes, the Activity is being terminated. But there doesn't seem to be any guarantee as to when this will happen. E.g. it won't necessarily happen before it's created again for the next test.


The Problem

In some of my tests, I need to rely on the fragments OnDestroy or OnDetach being called after each test, before the next test starts. I have listeners that need to be cleared and recreated.

If onDestroy from the previous test is called after OnResume in the current test, then the callback is cleared and the view doesn't get updated and the test fails.

If onDestroy from the previous test is not called at all, then the callback from the current test will be referring to the wrong instance. Again the view will not get updated and the test will fail.


The Question

  1. Is this behaviour discussed in the situation by design or is it a bug? I'm so far unable to find this in the documentation.
  2. What is best practice to handle this? I suspect other people have faced this problem.

Edit: I've now solved part 2. See workarounds section below. However if someone can answer part one by citing an official resource then I'd be happy to accept that answer. That's what I'm really asking here. The second part was just a bonus if someone had some ideas.


The Proof

If you would like to see this behaviour it will only take a few moments. Create a new project with an Activity like this:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

and a test class like this:

@RunWith(AndroidJUnit4.class)
@LargeTest
public class EspressoLifecycleTest {

    @Rule
    public ActivityTestRule<MainActivity> mActivityRule =
        new ActivityTestRule<>(MainActivity.class);

    @Test
    public void test1() {
    }

    @Test
    public void test2() {
    }

    @Test
    public void test3() {
    }

    @Test
    public void test4() {
    }
}

Put breakpoints on the OnResume and OnDestroy methods and run the test suite in debug mode.

Do this a few times and notice that the order the Activity life cycle methods are called is not consistent. E.g. it might call OnResume twice in a row, and then call OnDestroy once, and then OnResume twice again, and then OnDestroy three times, or any other combination you can think of. Of course it always starts with at least one OnResume. Sometimes it doesn't even call OnDestroy if it's at the very end, but that's fine. What's not fine is that my tests are flaky because of this unpredicatable order.

I'm aware that this might be intentional and there could be a simple way to deal with it, I'm just not lucky enough to have found it. Please if you know what it is, post the answer here. I don't care how dumb my question might be in hindsight, I've spent a LOT of time on this problem. It's almost always something simple so I'm prepared to be embarrassed by the answer.


Workarounds

Using onPause over OnDestroy has the side effect of being called when I startActivityForResult, but without calling onResume again in the background fragment while in tablet mode. I'm exploring ways to make this work but no solution as yet.

Edit: onPause ended up with the same problem - which is partly why I was using onDetach in the first place. Ultimately, there are times when I don't want to detach the listeners until the fragment is destroyed.

This leads me to my next idea which worked! Hooray! Up until now I was creating a callback for the calling Activity, for the thing it was asking for, only if that specific callback didn't exist. It turns out this was a bad idea. I did that so I could limit the number of callbacks to the exact number required. The motivation was sound but the implementation required all this callback clearing. The solution is to recreate every callback when ever it's called from the fragment. Don't create it if it's null, always create it and replace whatever was there before. Now there's no need to clear them at all (AFAIK).

like image 490
Michael Vescovo Avatar asked Mar 04 '17 03:03

Michael Vescovo


People also ask

How do you record an espresso test?

Record UI interactionsClick Run > Record Espresso Test. In the Select Deployment Target window, choose the device on which you want to record the test. If necessary, create a new Android Virtual Device. Click OK.

What is espresso software testing?

Espresso created by Google is a native framework for Android automated testing. The tool is a part of the Android SDK and is easy to use for native mobile development. Thanks to Espresso, you can create tests that are close to the Android app's logic.

What is espresso testing in Android and state the purpose of various components of espresso?

Espresso is a testing framework that helps developers write automation test cases for user interface (UI) testing. It has been developed by Google and aims to provide a simple yet powerful framework. It allows both black-box testing as well as testing of individual components during development cycles.


1 Answers

It's a bug: http://b.android.com/201513

I use fork work around it: https://github.com/shazam/fork

like image 57
chiuki Avatar answered Sep 18 '22 15:09

chiuki