I'm currently writing unit tests for an android application and stumbled into the following issue:
I use the ServiceTestCase to test an IntentService like this:
#Override
public void setUp() throws Exception {
super.setUp();
}
public void testService()
{
Intent intent = new Intent(getSystemContext(), MyIntentService.class);
super.startService(intent);
assertNotNull(getService());
}
However I noticed that my IntentService is created (means that onCreate is called) but I never receive a call into onHandleIntent(Intent intent)
Has anyone already tested an IntentService with the ServiceTestCase class?
Thanks!
This is a bit late, but I just struggled with this. You could solve this by creating a class that simply overrides the onStart of you service so it calls onHandleIntent directly. So for instance, if you have a LocationUpdaterService, you could create a fake class that overrides the onStart function like this:
public class LocationUpdaterServiceFake extends LocationUpdaterService {
#Override
public void onStart(Intent intent, int startId) {
onHandleIntent(intent);
stopSelf(startId);
}
LocationUpdaterService is a subclass of IntentService, so when you write your tests, just use the LocationUpdaterServiceFake class like this
public class LocationUpdateServiceTest extends ServiceTestCase<LocationUpdaterServiceFake> {
public LocationUpdateServiceTest()
{
super(LocationUpdaterServiceFake.class);
}
public void testNewAreaNullLocation()
{
Intent intent = new Intent();
intent.setAction(LocationUpdaterService.ACTION_NEW_AREA);
startService(intent);
}
}
Now whenever you call startService, it will bypass the threading code in IntentService and just call your onHandleIntent function
I just got started into testing my own IntentService and it's proving to be a bit of a headache.
Still trying to work things out but for the scenario where it seems that you do not receive a call to your method onHandleIntent(), (I'm not very good with the technicalities behind junit so forgive my use of terminology) it should be because the test framework, based on your code, actually tears down or end the test method once your call to startService returns. There is insufficient time for onHandleIntent to be triggered.
I verified the above theory by adding an infinite loop within my test case - only then can I see my log statements in onHandleIntent logged.
You just have to add a:
Thread.sleep(XXXXXXX);
Choose the XXXX after the startService, then it will let the thread go into the onHandleIntent method.
In Android Studio 1.1, when running tests using the Run/Debug Configuration | Android Tests facility on any unit under test code (UUT) that extends IntentService, the ServiceTestCase.java (JUnit?) code does not call onHandleIntent(Intent intent) method in the UUT. ServiceTestCase only calls onCreate so the problem is in the test code.
protected void startService(Intent intent) {
if (!mServiceAttached) {
setupService();
}
assertNotNull(mService);
if (!mServiceCreated) {
mService.onCreate();
mServiceCreated = true;
}
mService.onStartCommand(intent, 0, mServiceId);
mServiceStarted = true;
}
In my file smSimulatorTest.java:
public class smSimulatorTest extends ServiceTestCase<smSimulator>
At this point, I'm looking for other solutions in the testing framework that test UUTs through Intents since this is how IntentService is instantiated.
http://developer.android.com/reference/android/app/IntentService.html - To use it, extend IntentService and implement onHandleIntent(Intent). IntentService will receive the Intents, launch a worker thread, and stop the service as appropriate.
I, like others, put my code in the onHandleintent() as directed by the above documentation, however, ServiceTestCase only tests onStart and onStartCommand has shown above.
This is my approach for now:
The start Intent that invokes the service specifies the Service method to test
public void test_can_do_the_work() {
Intent startIntent = new Intent();
startIntent.putExtra("IN_TEST_MODE", "TEST_SPECIFIC_METHOD");
startIntent.setClass(getContext(), MyServiceToTest.class);
startService(startIntent);
assertNotNull(getService()); // Your assertion Service specific assertion
}
In the service onStart, we check for the specific Extra passed and call the method to test. This won't execute when Handle intent fired.
#Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
String in_test_mode = intent.getStringExtra("TEST_SPECIFIC_METHOD");
if(in_test_mode != null){
doServiceWork();
}
}
Related
I had setup AlarmManager in my MainActivity class.
A class called AlarmReceiver gets fired up for every set interval of time.
I have to perform an operation when that class is fired up. That code is in in another class Parsing.java
Now in AlarmReceiver.java, I'm doing this :
public class AlarmReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Parsing obj = new Parsing(context);
obj.execute();
}
}
I cannot write the code directly in AlarmReceiver.java, because AlarmReceiver.java is already extending BroadcastReceiverand my code which is Parsing.java is extending another class.
So, I'm creating an object for Parsing class and calling that method.
Is my approach correct?
I'll furnish further information in case needed.
Please let me know if my approach is correct?
Thanks in advance!
EDIT:
Parsing.java
public class Parsing extends AsyncTask<Void, Void, Void> {
//some code
}
Starting an AsyncTask from a BroadcastReceiver is wrong for two reasons:
1. The thread on which onReceive() runs is terminated after the method returns, effectively ending any long-running task which may have been started from there. To quote the official docs:
A BroadcastReceiver object is only valid for the duration of the
call to onReceive(Context, Intent). Once your code returns from this
function, the system considers the object to be finished and no longer
active ..... anything that requires asynchronous operation is not
available, because you will need to return from the function to handle
the asynchronous operation, but at that point the BroadcastReceiver
is no longer active and thus the system is free to kill its process
before the asynchronous operation completes.
2. The Context instance that onReceive() provides is not the same as
the Context of an Activity or Service, i.e. Activity.this or
Service.this. You need that proper Context for performing many of
the common useful operations that we usually do from an Activity or
Service. So, for example, the correct way to start a Service in
onReceive() is:
#Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context.getApplicationContext(), ParsingService.class);
context.getApplicationContext().startService(i);
}
and not
#Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, ParsingService.class);
context.startService(i);
}
I don't know how you wrote your Parsing.java, it looks fine but remember this
This method is always called within the main thread of its process, unless you explicitly asked for it to be scheduled on a different thread using registerReceiver. When it runs on the main thread you should never perform long-running operations in it (there is a timeout of 10 seconds that the system allows before considering the receiver to be blocked and a candidate to be killed). You cannot launch a popup dialog in your implementation of onReceive()
To me, i think it's a better way to handle this is calling another service inside onReceive method, like this
#Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, ParsingService.class);
context.startService(i);
}
In my Android project I have an Activity:
public MyActivity extends Activity{
...
#Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, MyService.class)
startService(intent);
}
}
In onStart() of MyActivity, I just starts MyService.
My simple service is just used to listen to phone state change:
public MyService extends Service{
#Override
public int onStartCommand(Intent intent, int arg, int id) {
super.onStartCommand(intent, arg, id);
/*register a PhoneStateListener to TelephonyManager*/
startToListenToPhoneState();// show Toast message for phone state change
return START_STICKY;
}
}
Everything works fine, after launch my app, when I make a phone call, my service is listening to phone state change & show toast messages.
NEXT, I decide to unit test my project, so I created a AndroidTestCase in my test project:
public class MySimpleTest extends AndroidTestCase{
...
#Override
protected void runTest() {
//make a phone call
String url = "tel:3333";
Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(url));
mContext.startActivity(mIntent);
}
}
The above test case simply starts a phone call, and it works fine too.
I press the HOME button to bring my app to background, after which, I run the test case to start a phone call, I was expecting that the PhoneStateListener in my service would still be running to show me the Toast message, but it didn't.
Then I figured out that I have to either start or bind MyService in my test case too, after which I am able to see the toast message from PhoneStateListener when run my test case, Why is that? I mean Why my service is running in background with my app but I still have to start or bind the service in test case in order to trigger the PhoneStateLister defined in MyService when running a AndroidTestCase?
In Android ServiceTestCase documentation, it says,
The test case waits to call onCreate() until one of your test methods calls startService(Intent) or bindService(Intent). This gives you an opportunity to set up or adjust any additional framework or test logic before you test the running service.
I think AndroidTestCase provides a framework which you can test your activities, services and so on in a controlled environment so that you should at least start your service before you test interacting with your service.
Reference: http://developer.android.com/reference/android/test/ServiceTestCase.html
Using Robolectric, how would one go about testing an IntentService that broadcasts intents as a response?
Assuming the following class:
class MyService extends IntentService {
#Override
protected void onHandleIntent(Intent intent) {
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent("action"));
}
}
In my test case, I'm attempting to do something like this:
#RunWith(RobolectricTestRunner.class)
public class MyServiceTest{
#Test
public void testPurchaseHappyPath() throws Exception {
Context context = new Activity();
// register broadcast receiver
BroadcastReceiver br = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// test logic to ensure that this is called
}
};
context.registerReceiver(br, new IntentFilter("action"));
// This doesn't work
context.startService(new Intent(context, MyService.class));
}
}
MyService is never started using this approach. I'm relatively new to Robolectric, so I'm probably missing something obvious. Is there some sort of binding I have to do before calling startService? I've verified that broadcasting works by just calling sendBroadcast on the context. Any ideas?
You can't test the service initialization like you're trying to do. When you create a new activity under Robolectric, the activity you get back is actually a ShadowActivity (kind of). That means when you call startService, the method that actually gets executed is this one, which just calls into ShadowApplication#startService. This is the contents of that method:
#Implementation
#Override
public ComponentName startService(Intent intent) {
startedServices.add(intent);
return new ComponentName("some.service.package", "SomeServiceName-FIXME");
}
You'll notice that it doesn't actually try to start your service at all. It just notes that you attempted to start the service. This is useful for the case that some code under test should start the service.
If you want to test the actual service, I think you need to simulate the service lifecycle for the initialization bit. Something like this might work:
#RunWith(RobolectricTestRunner.class)
public class MyServiceTest{
#Test
public void testPurchaseHappyPath() throws Exception {
Intent startIntent = new Intent(Robolectric.application, MyService.class);
MyService service = new MyService();
service.onCreate();
service.onStartCommand(startIntent, 0, 42);
// TODO: test test test
service.onDestroy();
}
}
I'm not familiar with how Robolectric treats BroadcastReceivers, so I left it out.
EDIT: It might make even more sense to do the service creation/destruction in JUnit #Before/#After methods, which would allow your test to only contain the onStartCommand and "test test test" bits.
I have an intentservice that gets qued by the user and by my app automatically. I need to be able to kill all pending intents that are qued when the user logs out of my application, but I cannot seem to get that to work. I have tried stopService() and stopself(), but the intents continue to fire off the intentservice after the user has logged out. I would try to get the id of the intent but that is difficult as everytime the intentservice starts, the variable holding the intent id's is empty. Here is my intentservice code:
public class MainUploadIntentService extends IntentService {
private final String TAG = "MAINUPLOADINTSER";
private GMLHandsetApplication app = null;
private SimpleDateFormat sdf = null;
public boolean recStops = true;
public MainUploadIntentService() {
super("Main Upload Intent Service");
GMLHandsetApplication.writeToLogs(TAG,
"GMLMainUploadIntentService Constructor");
}
#Override
protected void onHandleIntent(Intent intent) {
GMLHandsetApplication.writeToLogs(TAG, "onHandleIntent Started");
if (app == null) {
app = (GMLHandsetApplication) getApplication();
}
uploadData(app);
GMLHandsetApplication.writeToLogs(TAG, "onHandleIntent Finished");
}
#Override
public void onDestroy() {
GMLHandsetApplication.writeToLogs(TAG, "onDestroy Started");
app = null;
stopSelf();
GMLHandsetApplication.writeToLogs(TAG, "onDestroy completed");
}
public void uploadData(GMLHandsetApplication appl) {
//All of my code that needs to be ran
}
Unfortunately, I don't think it's possible to accomplish that with the standard IntentService methods since it doesn't offer a way to interrupt it while it's already going.
There are a few options I can think of that you can try to see if they fit your need.
Copy the IntentService code to make your own modifications to it that would allow you to remove pending messages. Looks like someone had some success with that here: Android: intentservice, how abort or skip a task in the handleintent queue
Instead of copying all the IntentService code, you might also be able to Bind to it like a normal Service (since IntentService extends Service) so you can write your own function to remove pending messages. This one is also mentioned in that link.
Rewrite the IntentService as a regular Service instead. With this option, you'd have more control over adding and removing messages.
I had what sounds like a similar situation where I was using an IntentService, and I eventually just converted it to a Service instead. That let me run the tasks concurrently and also cancel them when I needed to clear them.
Here
When should I free the native (Android NDK) handles? is the HangAroundIntentService class that has the method cancelQueue().
The class also has the method
public static Intent markedAsCancelIntent(Intent intent)
that converts an intent into a cancel intent, and
public static boolean isCancelIntent(Intent intent).
The class is based on the open-sourced Google's code.
Just a thought but inside of your onhandleintent can you have an argument that checks to see if app is running if not then don't run the code? example. In the start of your app you could have a static var
boolean appRunning;
Next in your onhandle of the intent, when you set the appRunning to false, after an onPause or onDestroy of activity, you could wrap the onhandleintent code in a boolean:
protected void onHandleIntent(final Intent intent) {
if(MainActivity.appRunning){
...
}
}
Just a thought
I am getting confused with all the different terminology when using Android: Activity, Service...
Right now I create a service:
startService(new Intent(this, RingerServer.class));
And this service starts a thread:
public class RingerServer extends Service {
public void onCreate() {
super.onCreate();
new Thread(new Ringer()).start();
}
public class Ringer implements Runnable { ... }
public void refuseConnection() { ... }
}
In this service, the RingerServer, I also have methods that I want to use. I would like to keep a reference to the RingerServer. I would basically like the Activity that created the service to be able to call refuseConnection(), but not make that method static.
startService returns a ComponentName, so I've been trying to cast it back to RingerServer but that doesn't seem to work. I see that it has getClass() and I've checked and getClassName() gives me the correct class. I haven't been able to use getClass() properly though.
Is there any way I can please keep a reference to the newly created RingerServer class? I am sure this is trivial, but I am stuck right now.
Thank you very much,
James
You have two options
1.Override onStartCommand of the service and start the server with intent using an action. that intent will be received in service, based on the intent action you can call refuseConnection()
//In Activity
...
//Start the service
Intent intent=new Intent("com.xx.xx.REFUSE_CONNECTION");
startService(this,intent);
...
//In Service
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
if(intent.getAction().equals("com.xx.xx.REFUSE_CONNECTION")){
//Refuse the connection
refuseConnection();
}else {
//Do something else
}
}
//In Manifest
<service android:name="RingerService">
<intent-filter>
<action android:name="com.xx.xx.REFUSE_CONNECTION"></action>
</intent-filter>
</service>
Implement AIDL interface and override onBind() of service , and use this interface to call refuseConnection(). Refer to this link http://developer.android.com/guide/developing/tools/aidl.html regarding AIDL.
You can use a ServiceConnection to get access to your service class. See sample code here:
Android service running after pressing Home key
That said, managing things via the service's onStart handler is much simpler.