Avoid Thread Issues While Testing an Android Service
The Android Test Framework provides many tools to test parts of an Android application, and the ServiceTestCase in particular to test your Service classes.
This class is quite useful but you may find yourself scratching your head because your test does not work like it should. This happens in particular if you’re doing some background work in your service, relying for example on AsyncTask for it.
Read on if you want to understand why it doesn’t work and find a solution for it.
In an Android application, any service is instantiated and operates on the main
thread. But this is not the case in the test framework provided by the
ServiceTestCase
class. Your Service is instantiated in the same thread the
test runs.
While your tests are running, there is no Looper waiting for messages on the service thread. In consequence, anything that relies on it and on the Handler class to communicate back to the main thread will not work.
For instance, AsyncTask
uses a handler to ensure that the onPostExecute
method is called on the main thread. After doInBackground
has been called, it
posts a message on this handler, but as the Looper
on the service is not
running to handle the message, the onPostExecute
method will never be called.
To circumvent this behaviour, the service must be run on a separate thread with
a Looper
running.
Simulating main thread behaviour
The ThreadServiceTestCase<T extends Service>
(source here) class that we
describe here provides such features. It declares a Looper
and a Hanlder
to
be able to run code on it:
|
|
In the setup of the test, we instantiate the service thread, start it, and link our handler with its looper:
|
|
The corresponding tearDown
method shuts down the tread.
We provide a runOnServiceThread
method to be able to run code on the service
thread:
|
|
Then, the startService
methods starts the service in its own thread:
|
|
The bound
parameters tells wether to start the service with a binding or with
an Intent
.. The optional ServiceRunnable
parameter can be provided to add
some initialization code.
A test class using this code looks like the following:
|
|
With it, the service is started in its own thread and the Looper
and Handler
mechanism will work.
Waiting for listeners to be notified
A service that performs tasks asynchronously also notifies the outcome of the background tasks asynchronously. There are several techniques for doing that, but the most common are :
- Broadcast an intent, or
- Call a callback method on listeners.
This usually happens in the main thread. In our case, it would happen in the service thread. As the test is executing itself in its own thread, some synchronization mechanism is needed between the service thread and the test thread to be able to handle the outcome of the background task in the test.
The ThreadServiceTestCase
class provides an helper class for that:
|
|
It contains a semaphore that can be used to synchronize the service thread with the test thread.
In the case of the callback listener, we can then define an utility class like the following:
|
|
When notified by the service in the service thread, the contained listener releases the semaphore and awakes the test that is waiting on the semaphore.
The listener contained in the helper class also needs to be added to the service being tested at service initialization. A test using this feature then becomes :
|
|
You can grab The ThreadServiceTestCase<T extends Service>
source code
here. Hope it will help.