1

I am writing a simple class for an Android app to act as a timer which will update the Activity UI at regular intervals. The timer class is using a Handler postDelayed() function to execute a Runnable.

Here's timer the code:

public class MyTimer
{
    private MyTimerDelegate _delegate;
    private int _seconds = 0;

    public MyTimerDelegate delegate() {
        return _delegate;
    }

    public void setDelegate(MyTimerDelegate delegate) {
        _delegate = delegate;
    }

    public int seconds() {
        return _seconds;
    }

    public void setSeconds(int seconds) {
        if (seconds < 0)
            throw new IllegalArgumentException();
        _seconds = seconds;
    }

    public void start() throws Exception {
        if (_delegate == null)
            throw new Exception("A delegate has not been set.");

        _delegate.MyTimerStartedFromSeconds(this, _seconds);

        // Schedule the first timer fire.
        final Handler handler = new Handler();
        final MyTimer MyTimer = this;
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                // The timer fired. Decrement seconds.
                _seconds -= 1;
                // Inform the delegate.
                _delegate.MyTimer_countedDownToSeconds(MyTimer, _seconds);

                // If there are seconds remaining, schedule the next fire.
                // Otherwise, stop the timer.
                if (_seconds > 0) {
                    handler.postDelayed(this, 1000);
                } else {
                    _delegate.MyTimerStopped(MyTimer);
                }
            }
        }, 1000);
    }
}

My questions is: How can I write a JUnit 4 test to ensure that MyTimer is calling its delegate at each interval during the count down?

Here's what I have done so far:

public void testCountdownCallsDelegate() throws Exception {
    class MockMyTimerDelegate implements MyTimerDelegate {

        public int callbackCount = 0;

        @Override
        public void biteTimerStartedFromSeconds(BiteTimer biteTimer, int seconds) {
        }

        @Override
        public void biteTimerStopped(BiteTimer biteTimer) {
        }

        @Override
        public void biteTimer_countedDownToSeconds(BiteTimer biteTimer, int seconds) {
            callbackCount++;
        }

    }

    MockMyTimerDelegate delegate = new MockMyTimerDelegate();
    MyTimer timer = new MyTimer();
    timer.setDelegate(delegate);
    timer.setSeconds(1);
    timer.start();

    // Need some way to wait here and allow the timer to call its delegate...

    assertEquals("The timer should have called the delegate when counting down.", 1, delegate.callbackCount);
}

Obviously, I cannot block the thread with Thread.sleep() calls. Any ideas?

Found solution

This solution seems to work. I'm posting it here in case it helps someone else. Please improve it if you see ways to.

public void testMyTimerDelegateIsCalledForEachSecondDuringCountdown() throws Exception {
    // An object to sync our threads with.
    Object syncLock = new Object();

    // This thread class will perform the actual test.
    class TimerThread extends Thread
    {
        Object _syncLock;

        TimerThread(Object syncLock) {
            _syncLock = syncLock;
        }

        @Override
        public void run() {
            // Since MyTimer is calling Handler postDelayed(), we need a Looper.
            Looper.prepare();

            // myTimer was instantiated in setUp()
            myTimer.setDelegate(new MockMyTimerDelegate() {
                @Override
                public void myTimer_countedDownToSeconds(MyTimer myTimer, int seconds) {
                    countdownToSecondsCallCount++;

                    if (countdownToSecondsCallCount >= 3) {
                        synchronized (_syncLock) {
                             // The test is complete. Now the parent thread can continue and assert the results.
                            _syncLock.notify();
                        }
                    }
                }
            });

            myTimer.setSeconds(3);
            try {
                myTimer.start();
                Looper.loop();
            } catch (Exception e) {
            }
        }
    }

    // Run the test on its own thread and wait for it to finish.
    TimerThread t = new TimerThread(syncLock);
    t.start();
    synchronized (syncLock) {
        // Wait more time than should be needed, but timeout in case of failure.
        syncLock.wait(5000);
    }

    assertEquals("MyTimerDelegate should have been called 3 times during three second countdown.", 3, ((MockMyTimerDelegate) myTimer.delegate()).countdownToSecondsCallCount);
}
Eric Baker
  • 357
  • 4
  • 14
  • It seems I didn't look hard enough for similar questions. This post answers my question: http://stackoverflow.com/questions/7715978/android-unit-tests-with-multiple-threads – Eric Baker May 18 '12 at 17:15

0 Answers0