// Copyright 2016 Google, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package com.firebase.jobdispatcher; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.support.annotation.NonNull; import com.firebase.jobdispatcher.JobService.JobResult; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @SuppressWarnings("WrongConstant") @RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, manifest = Config.NONE, sdk = 21) public class ExecutionDelegatorTest { private Context mMockContext; private TestJobReceiver mReceiver; private ExecutionDelegator mExecutionDelegator; @Before public void setUp() { mMockContext = spy(RuntimeEnvironment.application); doReturn("com.example.foo").when(mMockContext).getPackageName(); mReceiver = new TestJobReceiver(); mExecutionDelegator = new ExecutionDelegator(mMockContext, mReceiver); } @Test public void testExecuteJob_sendsBroadcastWithJobAndMessage() throws Exception { for (JobInvocation input : TestUtil.getJobInvocationCombinations()) { verifyExecuteJob(input); } } private void verifyExecuteJob(JobInvocation input) throws Exception { reset(mMockContext); mReceiver.lastResult = -1; mReceiver.setLatch(new CountDownLatch(1)); mExecutionDelegator.executeJob(input); final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); final ArgumentCaptor<ServiceConnection> connCaptor = ArgumentCaptor.forClass(ServiceConnection.class); verify(mMockContext).bindService(intentCaptor.capture(), connCaptor.capture(), anyInt()); final Intent result = intentCaptor.getValue(); // verify the intent was sent to the right place assertEquals(input.getService(), result.getComponent().getClassName()); assertEquals(JobService.ACTION_EXECUTE, result.getAction()); final ServiceConnection connection = connCaptor.getValue(); ComponentName cname = mock(ComponentName.class); JobService.LocalBinder mockLocalBinder = mock(JobService.LocalBinder.class); final JobParameters[] out = new JobParameters[1]; JobService mockJobService = new JobService() { @Override public boolean onStartJob(JobParameters job) { out[0] = job; return false; } @Override public boolean onStopJob(JobParameters job) { return false; } }; when(mockLocalBinder.getService()).thenReturn(mockJobService); connection.onServiceConnected(cname, mockLocalBinder); TestUtil.assertJobsEqual(input, out[0]); // make sure the countdownlatch was decremented assertTrue(mReceiver.mLatch.await(1, TimeUnit.SECONDS)); // verify the lastResult was set correctly assertEquals(JobService.RESULT_SUCCESS, mReceiver.lastResult); } @Test public void testExecuteJob_handlesNull() { assertFalse("Expected calling triggerExecution on null to fail and return false", mExecutionDelegator.executeJob(null)); } @Test public void testHandleMessage_doesntCrashOnBadJobData() { JobInvocation j = new JobInvocation.Builder() .setService(TestJobService.class.getName()) .setTag("tag") .setTrigger(Trigger.NOW) .build(); mExecutionDelegator.executeJob(j); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); ArgumentCaptor<ServiceConnection> connCaptor = ArgumentCaptor.forClass(ServiceConnection.class); //noinspection WrongConstant verify(mMockContext).bindService(intentCaptor.capture(), connCaptor.capture(), anyInt()); Intent executeReq = intentCaptor.getValue(); assertEquals(JobService.ACTION_EXECUTE, executeReq.getAction()); } @Test public void onStop_mock() throws InterruptedException { JobInvocation job = new JobInvocation.Builder() .setTag("TAG") .setTrigger(Trigger.NOW) .setService(TestJobService.class.getName()) .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL) .build(); reset(mMockContext); mReceiver.lastResult = -1; mExecutionDelegator.executeJob(job); final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); final ArgumentCaptor<ServiceConnection> connCaptor = ArgumentCaptor.forClass(ServiceConnection.class); verify(mMockContext).bindService(intentCaptor.capture(), connCaptor.capture(), anyInt()); final Intent result = intentCaptor.getValue(); // verify the intent was sent to the right place assertEquals(job.getService(), result.getComponent().getClassName()); assertEquals(JobService.ACTION_EXECUTE, result.getAction()); final JobParameters[] out = new JobParameters[2]; JobService mockJobService = new JobService() { @Override public boolean onStartJob(JobParameters job) { out[0] = job; return true; } @Override public boolean onStopJob(JobParameters job) { out[1] = job; return false; } }; JobService.LocalBinder mockLocalBinder = mock(JobService.LocalBinder.class); when(mockLocalBinder.getService()).thenReturn(mockJobService); ComponentName componentName = mock(ComponentName.class); final ServiceConnection connection = connCaptor.getValue(); connection.onServiceConnected(componentName, mockLocalBinder); mExecutionDelegator.stopJob(job); TestUtil.assertJobsEqual(job, out[0]); TestUtil.assertJobsEqual(job, out[1]); } private final static class TestJobReceiver implements ExecutionDelegator.JobFinishedCallback { int lastResult; private CountDownLatch mLatch; @Override public void onJobFinished(@NonNull JobInvocation js, @JobResult int result) { lastResult = result; if (mLatch != null) { mLatch.countDown(); } } /** * Convenience method for tests. */ public void setLatch(CountDownLatch latch) { mLatch = latch; } } }