简体   繁体   English

如何使用Robolectric测试IntentService?

[英]How to test an IntentService with Robolectric?

I'm trying to test the onHandleIntent() method of an IntentService using Robolectric . 我想测试onHandleIntent()的方法IntentService使用Robolectric

I'm starting the service with: 我正在启动服务:

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);

seems like startedIntent is not null, but onHandleIntent() doesn't seem to be called. 似乎startedIntent不为null,但似乎没有调用onHandleIntent()

how should I test it ? 我该怎么测试呢?

Robolectric has a ServiceController that can go thru service lifecycle just like activity. Robolectric有一个ServiceController ,可以像活动一样通过服务生命周期。 This controller provides all methods to execute corresponding service callbacks (eg controller.attach().create().startCommand(0, 0).destroy() ). 该控制器提供执行相应服务回调的所有方法(例如controller.attach().create().startCommand(0, 0).destroy() )。

Theoretically we can expect that IntentService.onStartCommand() will trigger IntentService.onHandleIntent(Intent) , via its internal Handler . 从理论上讲,我们可以预期IntentService.onStartCommand()将通过其内部Handler触发IntentService.onHandleIntent(Intent) However this Handler uses a Looper which runs on a background thread, and I have no idea how to make this thread advance to next task. 但是这个Handler使用一个在后台线程上运行的Looper ,我不知道如何让这个线程前进到下一个任务。 A workaround would be to create TestService that mimics the same behavior, but triggers onHandleIntent(Intent) on main thread (thread used to run tests). 解决方法是创建模仿相同行为的TestService ,但在主线程(用于运行测试的线程onHandleIntent(Intent)上触发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 : Alternatively, it's quite straightforward to create a similar controller for IntentService, as follows: 更新 :或者,为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;
    }
}

onHandleIntent is a protected method so it can't be called directly. onHandleIntent是一个受保护的方法,因此无法直接调用它。

my solution was to extend the service class in my test case, override onHandleIntent making it public and calling super.onHandleIntent(intent) 我的解决方案是在我的测试用例中扩展服务类,覆盖onHandleIntent使其公开并调用super.onHandleIntent(intent)

then call onHandleIntent directly from the test case. 然后直接从测试用例中调用onHandleIntent

I think you have approached this the wrong way. 我认为你已经采取了错误的方式。 You are testing here: 你在这里测试:

Intent startedIntent = shadowActivity.getNextStartedService();
assertNotNull(startedIntent);

that startService() was called. startService()

You then must assume that Android has implemented their end correctly and that after calling startService() the service will indeed be started. 然后,您必须假设Android已正确实现其结束,并且在调用startService()该服务确实将启动。

I think that if you want to test the behavior of onHandleIntent() , you must just call it directly?? 我想如果你想测试onHandleIntent()的行为,你必须直接调用它?

I am not sure exactly... but I think the test you want to write would just be testing the Android framework. 我不确定...但我认为你想要编写的测试只是测试Android框架。 You should write two tests. 你应该写两个测试。

1) Test that startService() is called (as you have in your example). 1)测试是否调用了startService() (如您的示例所示)。

2) Test the behaviour of onHandleIntent() (by calling it directly). 2)测试onHandleIntent()的行为(通过直接调用它)。

I have very little experience with Robolectric but hopefully this is helpful. 我对Robolectric经验很少,但希望这很有帮助。

Cheers 干杯

I use this 我用这个

@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());
}

and then I run the test and all is ok 然后我运行测试,一切都很好

Robolectric 3.1+ Robolectric 3.1+

Create an instance of your service 创建服务实例

Call onHandleIntent directly 直接调用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);

Note: use Robolectric.buildService (not .buildIntentService ) even if you're testing an IntentService. 注意:即使您正在测试IntentService,也要使用Robolectric.buildService (而不是.buildIntentService )。 .buildIntentService is currently not working, as far as I can tell. 据我所知, .buildIntentService目前无法正常工作。

Update 2017-05-17: 更新2017-05-17:

Issue with buildIntentService will be fixed in future buildIntentService问题buildIntentService修复

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM