Situation
The official documentation here: https://google.imtqy.com/android-testing-support-library/docs/rules/index.html says:
“This rule provides functional testing of one action. An activity test will be run before each test annotated with @Test and before any method annotated with @Before. It will end after the test is completed and all methods annotated with @ At the end of the work . The activity being tested can be accessed during your test by calling ActivityTestRule # getActivity (). "
Technically, yes, the action ends. But there seems to be no guarantee as to when this will happen. . For example. this does not necessarily happen before it is created again for the next test.
Problem
In some of my tests, I need to rely on the OnDestroy or OnDetach fragments called after each test, before the next run starts. I have listeners that need to be cleaned and recreated.
If the onDestroy from the previous test is called after OnResume in the current test, then the callback is cleared and the view is not updated and the test fails.
If onDestroy from the previous test is not called at all, then the callback from the current test will refer to the wrong instance. Again, the view will not be updated, and the test will fail.
Question
- Is this behavior discussed in a design situation or is this a mistake? I can not find this in the documentation yet.
- What's the best way to handle this? I suspect other people have run into this problem.
Change Now I have solved part 2. See the workarounds section below. However, if someone can answer part one by referring to the official resource, I would be happy to accept this answer. This is what I really ask here. The second part was just a bonus if someone had some ideas.
Evidence
If you want to see this behavior, it only takes a few seconds. Create a new project with this action:
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 test class:
@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() { } }
Place breakpoints on the OnResume and OnDestroy methods and run the test suite in debug mode.
Do this several times and note that the order that the activity lifecycle methods invoke is incompatible. For example. it can call OnResume twice, and then call OnDestroy once, and then repeat OnResume again, and then OnDestroy three times, or any other combination that 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 it’s fine. What is bad is that my tests are flaky due to this unpredictable order.
I know this can be intentional, and there may be an easy way to handle it, I was just unlucky to find it. Please, if you know what it is, write the answer here. I don't care how stupid my question may be in retrospect, I spent a lot of time on this problem. It is almost always something simple, so I am ready to be embarrassed by the answer.
Bypass
Using onPause via OnDestroy has a side effect that is called when ActiveForResult is launched, but without calling onResume again in the background fragment in tablet mode. I am exploring ways to make this work, but there is no solution yet.
Edit: onPause ended up with the same problem, in part because I used onDetach in the first place. Ultimately, there are times when I don't want to disconnect the listeners until the fragment is destroyed.
This leads me to my next idea that worked! Hooray! So far, I have created a callback for the calling activity, for the thing that it requested, only if this special callback did not exist. Turns out it was a bad idea. I did this to limit the number of callbacks to the exact number required. The motivation was reliable, but implementation required all this callback clearing. The solution is to recreate each callback when it is called from a fragment. Do not create it if it is null, always create it and replace everything that was before. Now there is no need to clean them at all (AFAIK).