I have a JobService that is properly implemented and works fine in simple cases but I want to ensure the service is properly tested in all conditions.
I'd like to use an Instrumentation test case so that I can test the full flow from the scheduling of the job with getSystemService(JobScheduler.class).schedule(job) to the invocation of onCreate in my JobService and my onStartJob/onStopJob calls.
My JobService launches AsyncTasks sometimes (thereby returning true in onStartJob) and other times it returns false for work that's already done. I have various calls to jobFinished(mJobParameters, needsReschedule) and I'd like to ensure that they work properly as well.
I've been trying to get instrumentation tests working for the past couple days but the best I've been able to come up with is a test that schedules the job but the job never leaves the pending state. I have tried various configurations of waits/background threads to see if freeing the UI thread is what's needed but haven't had any success.
It also doesn't appear that Google has surfaced anything to properly test the full flow of this component which is surprising, given how they seem to be forcing everyone to use it as newer APIs are released.
I've seen What tools are available to test JobScheduler? but it's difficult to automate with adb (and I'm not interested in answers that use it).
Does anyone know a way to end to end test a JobService with the JobScheduler using Instrumentation tests?
Thanks to the Google devs being very responsive to answer my question here: https://issuetracker.google.com/issues/62543492, it's now clear how to do it!
It appears that part of the set up of the instrumentation test examples they provided is setting the running app as active and mostly learning about job state via cmd jobscheduler <command> invocations on the shell from the test
try {
SystemUtil.runShellCommand(getInstrumentation(), "cmd activity set-inactive "
+ mContext.getPackageName() + " false");
} catch (IOException e) {
Log.w("ConstraintTest", "Failed setting inactive false", e);
}
See the provided InstrumentationTestCase subclass they posted in the bug notes
Related
I am still new to android and working on an application that works on Android (API >= 21). This application displays data that is previously downloaded from a server on the local network so I need to implement a service to download the content, on-demand & Periodically, on the device.
SyncAdapter
I've implemented this "Downloader" using SyncAdapter and it was working fine but at the end the code was really verbose:
The application does not have ContentProvider. The content is downloaded as files.
It runs on a local closed network so there is no need for authentication.
The application had 3/4 extra classes that not doing any real job.
JobScheduler
After some reading and searching, I decided to go with JobScheduler.
"JobScheduler is guaranteed to get your job done." medium article
It looks easy and has clear API, I said, so I re-implemented my "Downloader" with JobScheduler and the result was really good. The application was able to download the content, writing a good log to trace errors and operations. The Job runs when the device is Idle and kicks off/stopped, on demand, as expected.
I used alarm manager to implement the periodical calls of the job and turning wifi on. Oh, I forgot to mention that the application is the responsible of turning on the wifi because it is inside a case and works as Kiosk
The problem is that there is a catch. This is not mentioned in the documentation, or I was blind not to see it, at all. The Job is limited to 1 minute only (1 minute on lolipop and more on Android >= 0) then the onStopJob() will be called. So my is cancelled before completing the download when the data is a little big. Of course I can keep my thread going and continue download in the background but in this case I can't benefit from the API to maintain a good log, reschedule my job and manage Wifi status.
I need another implementation
I need something similar to SyncAdapter or JobScheduler that runs when the Wifi is on and when the device is Idle.
I am not sure whether triggering a service from JobScheduler is the solution I am left with. I need a little certain information before implementing the same thing for the third time.
Any idea?
I have written code to run tasks on job scheduler. Testing it on real device works fine. But i wanted a way to unit test it so that i can be sure it is covering all conditions like job success/failure/ runs when conditions are met, periodic etc. Please suggest me a way to write unit tests for these scenarios
You can run jobs on-demand via adb, as well as force an immediate timeout of an executing job as though it had reached the end of its timeslice. See the output of
adb shell cmd jobscheduler
for a summary of useful commands.
Background
Google has multiple solutions for job/task scheduling, such as JobScheduler and GcmTaskService. Each has its own advantages and disadvantages.
Recently, Google presented a new library called "Firebase JobDispatcher".
The problem
Sadly, there is very little to read about this new API. In fact, it's really hard to find anything about it.
Only thing I've found is their announcement video and a sample. But even their, there is not much to know about this API.
The questions
Looking at previous questions, investigations and comparisons I had with the other APIs (here, for example), I'd like to ask how the new API works and know what to take into consideration when using it:
Can a job have parameters that stay with it and can even be modified when needed? They say in the sample "An optional Bundle of user-supplied extras. The default is an empty Bundle." Is this it? Can it be modified by the job upon execution of it?
Can jobs be re-scheduled easily? It is said "A boolean indicating whether the Job should repeat" . How can it be chosen when to re-schedule? I've tried the sample, and chose "Recurring", but it doesn't seem to run again, only once.
Can it be protected vs library's jobs (because of unique ids)?
Does it needs extra care when updating the app (as previous APIs did)? Can jobs still be scheduled after an update of the app? Testing on the sample, it seems the jobs are completely gone after an update of the app. Can it be avoided?
Does it need RECEIVE_BOOT_COMPLETED in case I want the job to still be scheduled even when the device is restarted? The sample seems to have it.
Is it possible to get a list of all scheduled jobs and their information (including parameters), and be able to cancel specific/all of them and even modify them ?
Will a job be removed upon clear-data operation of the app?
Is it possible to tell the job that it's best that it will run in a range of time (example : between 7:00 and 8:00 in the morning)? It is mentioned "ExecutionWindowTrigger-which specifies a time window in which the Job should be executed". Is that it? What happens when it misses this window?
The method onStartJob in JobService class return a boolean and the description for it is "whether there is more work remaining." What does it mean? What does the needsReschedule parameter of jobFinished method mean? Are they related to each other?
Are there any restrictions I should know about? For example minimal & maximal values for each of the functions?
Actually Firebase Android JobDispatcher is a layer of abstraction around job scheduling engines on Android.
And for now they only have one driver implementation for GCM Network Manager.
That means currently it behaves the same way as GCM Network Manager behaves. Hopefully in the future more Drivers will be implemented.
1. Can a job have parameters that stay with it and can even be modified when needed? They say in the sample "An optional Bundle of user-supplied extras. The default is an empty Bundle." . Is this it? Can it be modified by the job upon execution of it?
Yes, Job.Builder has method setExtras with arbitrary bundle which later may be accessed via jobParameters.getExtras().
You cannot modify the bundle (jobParameters contains only getters). You could reschedule your job with flag setReplaceCurrent(true) and specify a new bundle.
2. Can jobs be re-scheduled easily ? It is said "A boolean indicating whether the Job should repeat" . How can it be chosen when to re-schedule? I've tried the sample, and chose "Recurring", but it doesn't seem to run again, only once.
To re-schedule job you need specify setRecurring(true), setTrigger(Trigger.executionWindow(10, 20))
This becomes triggered as soon as the window start deadline is
reached, and drivers are encouraged to run the Job before the window
end if possible.
3. Can it be protected vs library's jobs (because of unique ids) ?
Job tags must be unique in your application. Other apps on the phone have their own 'endpoints' (package name/service name).
To see all scheduled/finished jobs for GooglePlayDriver please use
adb shell dumpsys activity service GcmService
4. Does it needs extra care when updating the app (as previous APIs did) ? Can jobs still be scheduled after an update of the app ? Testing on the sample, it seems the jobs are completely gone after an update of the app. Can it be avoided?
As for GCM Network Manager GooglePlayDriver doesn't reschedule Jobs after Google Play Services or the app is updated.
Here is an open issue for this. So for now this is your responsibility.
5. Does it need RECEIVE_BOOT_COMPLETED in case I want the job to still be scheduled even when the device is restarted? The sample seems to have it.
Yes you need such permission.
Builder has a parameter to control the behavior: setLifetime(Lifetime.FOREVER | UNTIL_NEXT_BOOT)
Of course if you're going to create your own driver you'll have to take care of the lifetime yourself.
6. Is it possible to get a list of all scheduled jobs and their information(including parameters), and be able to cancel specific/all of them and even modify them ?
No, the same as for GCM Network Manager.
But you could track all jobs yourself somehow while scheduling them to play services.
7. Will a job be removed upon clear-data operation of the app?
Yes, the job will be removed. Probably in the previous versions of google play services it behaved differently.
8. Is it possible to tell the job that it's best that it will run in a range of time (example : between 7:00 and 8:00 in the morning) ? It is mentioned "ExecutionWindowTrigger-which specifies a time window in which the Job should be executed" . Is that it? What happens when it misses this window?
Well, you could setup an alarm to be fired at 7:00 and schedule a non-recurring job with executionWindow(0, 60*60). The job will run between 7:00 - 8:00.
You cannot use recurring job because
windowStart - The earliest time (in seconds) the job should be
considered eligible to run. Calculated from when the job was scheduled
(for new jobs) or last run (for recurring jobs).
Also, ExecutionWindowTrigger specifies approximate time. It's not guaranteed it would run at the given window.
If it misses the window the job will run any time later.
9. The method "onStartJob" in "JobService" class return a boolean and the description for it is "whether there is more work remaining." . What does it mean? What does the "needsReschedule" parameter of "jobFinished" method mean? Are they related to each other?
if onStartJob returns false that means you completed your work. No Need to invoke jobFinished. The RESULT_SUCCESS is sent automatically.
if onStartJob returns true that means you started a thread and waiting for results. As soos as you're done you must invoke jobFinished to inform google play services whether the job should be rescheduled or not. If yes the job will be rescheduled depending on RetryStrategy.
10. Are there any restrictions I should know about? For example minimal&maximal values for each of the functions?
onStartJob should offload work to another thread of execution as soon as possible. They provide a SimpleJobService as an example of what is expected from you.
There is no Driver implementation for Lollipop's JobScheduler. Also need to handle the situation when google play services are not available, we should probably implement Driver based on AlarmManager.
I need to test a use case where the application starts from a clean state - i.e. the process has not been running before the test starts. From what I see from logcat, all instrumentation tests run under one single process instance/session, so the outcome of the test in my case depends on whether or not it runs as #1 or not. It should not be this way - as we all know, unit tests (or instrumentation tests) should be autonomous.
Is there any way with the standard Android instrumentation test tools and functions I can force the TestRunner to restart the process before a given test? If not, are there hacks or third-party libraries that can help me achieve that? Or is there any way I can specifically say that test X must be run first (worst option but still)?
In specific, my test relates to the launching of activities through intents, and the intent flags (e.g. FLAG_ACTIVITY_CLEAR_TOP) in addition to the Activity launch mode (e.g. singleTop) and the state of the process, very much dictates the outcome of the test.
Assuming you are running with Espresso, there is not a clean way to pull this off. This is because Espresso runs in the same process as the application and thus killing the app will kill Espresso.
The question is, do you need all the logic you want to execute in your Application or could it be ported to your Activity.onCreate()? With Espresso restarting an Activity is doable. If there is a need to restart the application because of global/singletons, removing these may be necessary. If this cannot be done you can look at other test automation frameworks like Appium which has some support for this.
How do you create unit tests for an Android activity that starts async tasks in onCreate? I would like to test the result of these tasks.
It is hard to write tests for a lot of Android functionality, since you can't instantiate classes like Activity outside of Android.
You might be better off doing a true unit test...test the function whose behavior you care about in isolation. Don't try to test it in the context of async task, activity, etc.
You might need to refactor your code a little bit to be able to do that, but its worth it to have testable code!
Running true units tests as mentioned in Cheryl's answer would be ideal. However if you still find yourself wanting to test the result AsyncTasks or any long running asynchronous operation in an Activity Test, Espresso is the silver bullet.
Espresso automatically waits for AyscTasks to complete and the developer can manually tell Espresso to wait for custom background tasks running via the IdlingResource APIs.
Here's a tutorial to help you get started: http://blog.sqisland.com/2015/04/espresso-custom-idling-resource.html
IdlingResource documentation: http://developer.android.com/reference/android/support/test/espresso/IdlingResource.html