Как протестировать IntentService с помощью Robolectric?
Я пытаюсь протестировать метод onHandleIntent()
IntentService
с помощью Robolectric
.
Я запускаю службу с помощью:
Activity activity = new Activity();
Intent intent = new Intent(activity, MyService.class);
activity.startService(intent);
ShadowActivity shadowActivity = Robolectric.shadowOf(activity);
Intent startedIntent = shadowActivity.getNextStartedService();
assertNotNull(startedIntent);
похоже, что startedIntent
не является нулевым, но onHandleIntent()
, похоже, не называется.
как я должен его проверить?
Ответы
Ответ 1
onHandleIntent
- защищенный метод, поэтому он не может быть вызван напрямую.
Мое решение состояло в том, чтобы расширить класс службы в моем тестовом случае, переопределить onHandleIntent
, сделав его общедоступным и вызвав super.onHandleIntent(intent)
затем вызовите onHandleIntent
непосредственно из тестового примера.
Ответ 2
Robolectric имеет ServiceController
, который может проходить через жизненный цикл службы так же, как и активность. Этот контроллер предоставляет все методы для выполнения соответствующих обратных вызовов службы (например, controller.attach().create().startCommand(0, 0).destroy()
).
Теоретически мы можем ожидать, что IntentService.onStartCommand()
вызовет IntentService.onHandleIntent(Intent)
через свой внутренний Handler
. Однако в этом Handler
используется Looper
, который работает в фоновом потоке, и я не знаю, как сделать этот поток для перехода к следующей задаче. Обходным решением было бы создать TestService
, который имитирует одно и то же поведение, но запускает onHandleIntent(Intent)
в основном потоке (поток, используемый для запуска тестов).
@RunWith(RobolectricGradleTestRunner.class)
public class MyIntentServiceTest {
private TestService service;
private ServiceController<TestService> controller;
@Before
public void setUp() {
controller = Robolectric.buildService(TestService.class);
service = controller.attach().create().get();
}
@Test
public void testWithIntent() {
Intent intent = new Intent(RuntimeEnvironment.application, TestService.class);
// add extras to intent
controller.withIntent(intent).startCommand(0, 0);
// assert here
}
@After
public void tearDown() {
controller.destroy();
}
public static class TestService extends MyIntentService {
public boolean enabled = true;
@Override
public void onStart(Intent intent, int startId) {
// same logic as in internal ServiceHandler.handleMessage()
// but runs on same thread as Service
onHandleIntent(intent);
stopSelf(startId);
}
}
}
UPDATE. Альтернативно, достаточно просто создать аналогичный контроллер для IntentService, как показано ниже:
public class IntentServiceController<T extends IntentService> extends ServiceController<T> {
public static <T extends IntentService> IntentServiceController<T> buildIntentService(Class<T> serviceClass) {
try {
return new IntentServiceController<>(Robolectric.getShadowsAdapter(), serviceClass);
} catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
}
private IntentServiceController(ShadowsAdapter shadowsAdapter, Class<T> serviceClass) throws IllegalAccessException, InstantiationException {
super(shadowsAdapter, serviceClass);
}
@Override
public IntentServiceController<T> withIntent(Intent intent) {
super.withIntent(intent);
return this;
}
@Override
public IntentServiceController<T> attach() {
super.attach();
return this;
}
@Override
public IntentServiceController<T> bind() {
super.bind();
return this;
}
@Override
public IntentServiceController<T> create() {
super.create();
return this;
}
@Override
public IntentServiceController<T> destroy() {
super.destroy();
return this;
}
@Override
public IntentServiceController<T> rebind() {
super.rebind();
return this;
}
@Override
public IntentServiceController<T> startCommand(int flags, int startId) {
super.startCommand(flags, startId);
return this;
}
@Override
public IntentServiceController<T> unbind() {
super.unbind();
return this;
}
public IntentServiceController<T> handleIntent() {
invokeWhilePaused("onHandleIntent", getIntent());
return this;
}
}
Ответ 3
Я думаю, вы подошли к этому неправильно.
Вы тестируете здесь:
Intent startedIntent = shadowActivity.getNextStartedService();
assertNotNull(startedIntent);
который был вызван startService()
.
Затем вы должны предположить, что Android правильно выполнил свой конец и после вызова startService()
служба действительно будет запущена.
Я думаю, что если вы хотите проверить поведение onHandleIntent()
, вы должны просто вызвать его напрямую.
Я точно не уверен... но я думаю, что тест, который вы хотите написать, просто будет тестировать платформу Android. Вы должны написать два теста.
1) Проверьте, что startService()
вызывается (как в вашем примере).
2) Проверьте поведение onHandleIntent()
(вызывая его напрямую).
У меня очень мало опыта работы с Robolectric
, но, надеюсь, это полезно.
Приветствия
Ответ 4
Я использую этот
@Before
public void setup(){
mainActivity = Robolectric.buildActivity(MainActivity.class)
.create()
.start()
.resume()
.get();
context = Robolectric.getShadowApplication().getApplicationContext();
bt_start = (Button) mainActivity.findViewById(R.id.button);
bt_stop = (Button) mainActivity.findViewById(R.id.button2);
}
@Test
public void serviceIsOn(){
bt_start.performClick();
Intent intent = Robolectric.shadowOf(mainActivity).peekNextStartedService();
assertEquals(MiService.class.getCanonicalName(),intent.getComponent().getClassName());
}
а затем я запускаю тест, и все в порядке
Ответ 5
Robolectric 3.1 +
Создайте экземпляр своей службы
Вызов onHandleIntent напрямую
YourService svc = Robolectric.buildService(YourService.class).get();
Intent fakeIntent = new Intent();
fakeIntent.putExtra(YourService.SOME_EXTRA, "debug|fail");
svc.onHandleIntent(fakeIntent);
assertThat(something.happened).isEqualTo(true);
Примечание. Используйте Robolectric.buildService
(не .buildIntentService
), даже если вы тестируете IntentService. .buildIntentService
в настоящее время не работает, насколько я могу судить.
Обновление 2017-05-17:
Проблема с buildIntentService
будет исправлена в будущем