moarCoffee
moarCoffee

Reputation: 1319

Testing if finish() is called in an instrumentation test

The activity being tested is for a simple board game. When the conditions necessary for the game to be over are met, after doing several things to display who won etc, the following code is run:

// Return to main menu after 5 seconds
Handler h = new Handler();
h.postDelayed(new Runnable() {
    @Override
    public void run() {
        finish();
    }
}, 5000);

I have an instrumentation test which uses ActivityInstrumentationTestCase2. I can manufacture it so that the test starts with the board one move away from the above code being called. But after doing that move how can I assert that finish() was called?

I've tried duplicating this test using an ActivityUnitTestCase which has the isFinishCalled() method, but I haven't been able to make it work.

Upvotes: 2

Views: 2426

Answers (1)

Konstantin Loginov
Konstantin Loginov

Reputation: 16000

I have 2 possible solution for this problem:

  1. Rewrite the test as Unit test with Robolectric framework:

    public class MainActivity extends AppCompatActivity {
        ........
    
        public void finishGame() {
            Handler h = new Handler();
            h.postDelayed(new Runnable() {
                @Override
                public void run() {
                    finish();
                }
            }, 5000);
        }
    }
    

    Then the test will look like:

    @RunWith(RobolectricTestRunner.class)
    @Config(constants = BuildConfig.class, sdk=21)
    public class MainActivityTest {
    
        @Test
        public void testFinishing() throws InterruptedException {
            MainActivity mainActivity = new MainActivity();
    
            assertFalse(mainActivity.isFinishing());
    
            mainActivity.finishGame();
            ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
    
            assertTrue(mainActivity.isFinishing());
        }
    }
    
  2. Avoid Handler in unit tests only:

    public class MainActivity extends AppCompatActivity {
        ........
    
        public void finishGame(boolean postDelayed) {
            if (postDelayed) {
                Handler h = new Handler();
                h.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        finish();
                    }
                }, 5000);
    
                return;
            }
    
            finish();
        }
    }
    

    Then the test will look like:

    public class MainActivityTest extends ActivityTestCase {
    
        public MainActivityTest() {
            super();
            setActivity(new MainActivity());
        }
    
        public void testFinishing() {
            assertFalse(getActivity().isFinishing());
            ((MainActivity)getActivity()).finishGame(false);
            assertTrue(getActivity().isFinishing());
        }
    }
    

    And in production code you call this method like finishGame(true).

In my project, I'm sticking to the second option (in my case, I'm testing FragmentTransaction's behavior, with also required delay for ensuring the view is properly constructed)

I hope, it helps

Upvotes: 2

Related Questions