Handler.handleMessage is not called in test, but i

2020-06-23 08:20发布

问题:

I have a service that runs in a separate process. The service spawns a new thread in onCreate() method. This thread sends messages back to the service.

If I start the app manually everything works fine - messages are received by the Handler in my service. But in my test handleMessage() method is never get called.

How can I fix my test to make handleMessage() method work?

Thanks!

Service:

public class MyService extends Service {

    private ServiceHandler mHandler;
    private final int MSG_HELLO_WORLD = 1;

    private final String TAG = getClass().getSimpleName();

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return null;
    }


    @Override
    public void onCreate() {
        Log.d(TAG, "MyService.onCreate");
        super.onCreate();

        mHandler = new ServiceHandler();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "Thread: run()");

                while (true) {

                    try {
                        Log.d(TAG, "sleeping...");
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        Log.d(TAG, "Thread was interrupted.");
                        break;
                    }

                    Log.d(TAG, "sending message...");
                    Message msg = Message.obtain(mHandler, MSG_HELLO_WORLD);
                    msg.sendToTarget();
                }
            }
        }).start();

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "MyService.onStartCommand");
        return START_REDELIVER_INTENT;
    }

    private class ServiceHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, "ServiceHandler.handleMessage");

            if (msg.what == MSG_HELLO_WORLD) {
                Log.d(TAG, "Hello World message received!");
            }
        }
    }

}

Test:

public class MyServiceTest extends ServiceTestCase<MyService> {

    private static final String TAG = MyService.class.getSimpleName();

    public MyServiceTest() {
        super(MyService.class);
    }


    public void setUp() throws Exception {
        super.setUp();
    }


    public void testStartService() throws Exception {

        Intent startServiceIntent = new Intent(getContext(), MyService.class);

        startService(startServiceIntent);

        MyService myService = getService();

        assertNotNull(myService);

        // give the service some time to send messages
        Thread.sleep(10000);
    }
}

App's logcat output (handleMessage() is called):

MyService.onCreate
MyService.onStartCommand
Thread: run()
sleeping...
sending message...
sleeping...
ServiceHandler.handleMessage
Hello World message received!
sending message...
sleeping...

Test's logcat output (handleMessage() is not called):

MyService.onCreate
MyService.onStartCommand
Thread: run()
sleeping...
sending message...
sleeping...
sending message...
sleeping...
sending message...
sleeping...

回答1:

Your question seemed interesting to me, so I started digging.

If I start the app manually everything works fine - messages are received by the Handler in my service.

When you initialize mHandler in the MyService's onCreate() method with mHandler = new ServiceHandler(), the Handler is associated with the message queue of the currently executed thread. mHandler process Messages from the queue that, in its turn, is handled by Looper which is looping. As the result you can see "Hello World message received!" in logs.

But in my test handleMessage() method is never get called.

When you test the service its methods are called on InstrumentationThread (see the threads in Debugger) whose Looper is prepared but not looping, so the messages can't be handled and aren't shown in logs.

How can I fix my test to make handleMessage() method work?

I'd suggest the following options:

  • Tie MyServiceTest to the thread on which MyService's methods are called. Write one more unit test for the component and then combine the test with MyServiceTest in one common .java test file. This will show the desired logcat output.
  • Loop the prepared Looper of InstrumentationThread in MyServiceTest. You can do it by adding Looper.loop() in the testStartService() method of MyServiceTest:

    public void testStartService() throws Exception {
    
        Intent startServiceIntent = new Intent(getContext(), MyService.class);
        startService(startServiceIntent);
        MyService myService = getService();
        assertNotNull(myService);
    
        // Run the Looper (this call is to be added)
        Looper.loop();
    
        Thread.sleep(10000);
    }
    

    Note: running the Looper will show the desired logcat output, but the test won't stop because of Looper.loop() running indefinitely until you call Looper.quit().