Espresso not waiting till Activity is destroyed, b

2019-02-21 21:06发布

问题:

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).

回答1:

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

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



回答2:

Noticed this issue before and the 'solution' I can think of is to override methods in ActivityTestRule: afterActivityFinished() or beforeActivityLaunched(). Basically you want to check and wait the listeners are cleared before next test execution.

IMO, this is a bug of ActivityTestRule.