6

I have a problem with unit tests in Android.

My object MyObject has a method start() like this :

public void start() {
    final Handler onStartHandler = new Handler();
    new Thread() {
        @Override
        public void run() {
            super.run();

            onStartHandler.post(new Runnable() {
                @Override
                public void run() {
                    mIsRunning = true;
                    onStart();
                }
            });
        }
    }.start();
}

And I want to test that onStart() is called. So I tried something like that :

public void testOnStartIsCalled() {
    assertFalse("onStart() should not be called", mMyObject.isRunning());
    mMyObject.start();
    assertTrue("onStart() should be called", mMyObject.isRunning());
    mMyObject.stop();
    assertFalse("onStop() should be called", mMyObject.isRunning());
}

But it doesn't work, I guess it's because it's in a Handler and a new Thread.

My test class extends AndroidTestCase. What should I do ? What is the best practice for this case ?

Regards.

louiscoquio
  • 10,638
  • 3
  • 33
  • 51

2 Answers2

9

When I deal with testing some multi-threaded code I try to let the program take as much of its natural flow as possible. Additionally, I avoid the use of sleep statements since you don't get any guarantees that the sleep interval you've chosen is enough to allow the subject of your test to finish what it's doing; you often end up having to choose sleep intervals that are too large and it forces a much slower execution of your test cases.

I would recommend that you try to add some code into the class you're testing, in this case MyObject, which call a listener whenever something happens. It seems that you already have callback methods for onStart() and onStop()(if those are events/callbacks), so those should be getting invoked and you should use them to control the flow of your test. When you get an onStart() event, you should then call stop() and wait for an onStop() event.

Update

First and foremost, you have redundant code:

public void start() {
    final Handler onStartHandler = new Handler();
    new Thread() {
        @Override
        public void run() {
            super.run();

            onStartHandler.post(new Runnable() {
                @Override
                public void run() {
                    mIsRunning = true;
                    onStart();
                }
            });
        }
    }.start();
}

Either start a new thread to call onStart() or schedule the runnable on the Handler's thread queue.

Version 1- remove the handler and just let the code be executed in a new thread:

public void start() {
    new Thread() {
        @Override
        public void run() {
            super.run();
            mIsRunning = true;
            onStart();
        }
    }.start();
}

Version 2- only use the handler to asynchronously execute the callback:

public void start() {
    final Handler onStartHandler = new Handler();

    onStartHandler.post(new Runnable() {
        @Override
        public void run() {
            mIsRunning = true;
            onStart();
        }
    });
}

And second: I noticed is that if you don't have a Looper, then whatever you post with the Handler will be ignored (thus it will never be called). For more information on the Looper-Handler pattern see the article: Android Guts: Intro to Loopers and Handlers. The Looper and the Handler are supposed to be attached to the same thread (usually the main thread). Additionally, if you're creating the Handler on a separate thread as your Looper, then you'll run into the same problem: anything you post with the Handler will be ignored.

Here are a few more good questions and articles on loopers and handlers:

The relationships between Looper, Handler and MessageQueue is shown below: enter image description here

Community
  • 1
  • 1
Kiril
  • 39,672
  • 31
  • 167
  • 226
  • yep, but the fact is that I want to test IF callbacks onStart() & onStop() are called. And I think that modify code for test it isn't a good idea. It seems like the Runnable has never ran.. – louiscoquio Oct 11 '11 at 07:29
  • 1
    You don't need to modify the actual class, you already have the callback methods so just add another `Handler` which will receive callbacks when the `onStart` event is raised. That handler will call `notify()` on a signal object (say `onStartSignal`) by calling `onStartSignal.notify()`. The test case will call `onStartSignal.wait(timeoutMilliseconds)` and it will only wait up to the specified timeout time; if you don't receive the callback within the allocated timeout then you fail the test case. – Kiril Oct 11 '11 at 12:35
  • I guess it could work, but `onStart()` is never called, the Runnable has never ran...But my code is OK, it works in real situation.. – louiscoquio Oct 11 '11 at 13:58
  • 1
    `onStart()` is never called because there is nothing that's going to call it. The `Handler` doesn't execute the `Runnable`, it only queues it for execution and a `Looper` is supposed to loop through the queue and execute all the runnables that were queued. See my update for more info. – Kiril Oct 11 '11 at 16:24
  • 1
    @Lirik "if you don't have a Looper, then whatever you post with the Handler will be ignored"- that's what I'm noticing too. But I can't find any documentation that says why posts to the handler don't work in an ActivityUnitTestCase. I'm still looking for documentation so I can decide if this is true, or perhaps I have something wrong in my code somewhere. – flobacca Apr 05 '14 at 19:53
0

The problem here is that you are calling onStart() which invokes a new thread, and then immediately ask if it is started. There is startup time for the new thread and while that is happening, your test is asking if it is started -- it's not YET.

I bet if you waited by using Thread.sleep(), or a loop, you'd find it is started "eventually".

What is it you're actually trying to test?

If you need the new thread, you might want to read up on threads, synchronize, etc. http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html

tarrant
  • 2,633
  • 1
  • 17
  • 10
  • I'm trying to test if callbacks are called ( I edited my question and added callbacks ). I don't want to use Thread.sleep() because I don't really know when it will be ok..But yet, the Runnable has never ran – louiscoquio Oct 11 '11 at 07:31