Android:在检测测试中获取对已启动服务的引用

Android:在检测测试中获取对已启动服务的引用,android,unit-testing,android-service,android-testing,Android,Unit Testing,Android Service,Android Testing,我正在尝试为我的NetworkMonitorService编写插装测试,如官方“”文档中所述 目前我陷入困境,因为我不知道如何获取对已启动服务的引用,以便将mock注入其中并断言行为 我的代码: @RunWith(AndroidJUnit4.class) @SmallTest public class NetworkMonitorServiceTest { @Rule public final ServiceTestRule mServiceTestRule = new Service

我正在尝试为我的
NetworkMonitorService
编写插装测试,如官方“”文档中所述

目前我陷入困境,因为我不知道如何获取对已启动服务的引用,以便将mock注入其中并断言行为

我的代码:

@RunWith(AndroidJUnit4.class)
@SmallTest
public class NetworkMonitorServiceTest {

    @Rule public final ServiceTestRule mServiceTestRule = new ServiceTestRule();

    @Test
    public void serviceStarted_someEventHappenedInOnStartCommand() {
        try {
            mServiceTestRule.startService(new Intent(
                    InstrumentationRegistry.getTargetContext(),
                    NetworkMonitorService.class));
        } catch (TimeoutException e) {
            throw new RuntimeException("timed out");
        }

        // I need a reference to the started service in order to assert that some event happened
        // in onStartCommand()...
    }
}
该服务不支持绑定。我认为,如果我实现对绑定的支持,然后在测试中使用它,以便获得对服务的引用,它就可以工作。然而,我不喜欢仅仅为了支持测试用例而编写生产代码


那么,如何测试(仪器测试)一个不支持绑定的
服务呢?

用特殊版本的“for tests”替换您的应用程序。通过提供定制的仪器测试运行程序来实现这一点。模拟您的依赖项,并将其用于此“测试应用程序”

下面是一个如何使用“测试应用程序”的简化示例。假设您希望在测试期间模拟网络层(例如,
Api

public class App extends Application {
    public Api getApi() {
        return realApi;
    }
}

public class MySerice extends Service {
    private Api api;
    @Override public void onCreate() {
        super.onCreate();
        api = ((App) getApplication()).getApi();
    }
}

public class TestApp extends App {
    private Api mockApi;

    @Override public Api getApi() {
        return mockApi;
    }

    public void setMockApi(Api api) {
        mockApi = api;
    }
}

public class MyTest {
    @Rule public final ServiceTestRule mServiceTestRule = new ServiceTestRule();

    @Before public setUp() {
        myMockApi = ... // init mock Api
        ((TestApp)InstrumentationRegistry.getTargetContext()).setMockApi(myMockApi);
    }

    @Test public test() {
        //start service
        //use mockApi for assertions
    }
}

在本例中,依赖项注入是通过应用程序的方法
getApi
完成的。但是你也可以用Dagger或其他任何类似的方法。

我找到了一个非常简单的方法。您只需执行一个绑定,就可以获得对已经运行的服务的引用,这与服务创建没有冲突,因为您已经使用
onStartCommand
启动了它,如果您进行检查,您将看到
onCreate
只调用了一次,因此您可以确保它是同一个服务。只需在示例之后添加以下内容:

    Intent serviceIntent =
            new Intent(InstrumentationRegistry.getTargetContext(),
                    NetworkMonitorService.class);

    // Bind the service and grab a reference to the binder.
    IBinder binder = mServiceRule.bindService(serviceIntent);

    // Get the reference to the service
    NetworkMonitorService service =
            ((NetworkMonitorService.LocalBinder) binder).getService();

    // Verify that the service is working correctly however you need
    assertThat(service, is(any(Object.class)));

我希望它能有所帮助。

这至少对绑定服务有效:

@Test
public void testNetworkMonitorService() throws TimeoutException {

    Intent intent = new Intent(InstrumentationRegistry.getTargetContext(), NetworkMonitorService.class);
    mServiceRule.startService(intent);

    IBinder binder = mServiceRule.bindService(intent);
    NetworkMonitorService service = ((NetworkMonitorService.LocalBinder) binder).getService();

    mServiceRule.unbindService();
}

要访问字段,请使用
@VisibleForTesting(否则=VisibleForTesting.NONE)注释

将应用程序替换为特殊版本的“for tests”。通过提供定制的仪器测试运行程序来实现这一点。模拟您的依赖项,并将其用于此“测试应用程序”。有关@MyDogTom的详细信息,我考虑过这种方法,但我看不到一种简单的方法可以通过这种方式模拟服务的依赖性。您能提供一些代码示例吗?谢谢,但我已经尝试过这种方法。这里的问题是,它不允许为每个测试注入模拟-您为所有将要运行的测试指定模拟。这可能适用于小型应用程序,但在大型应用程序中(我的Dagger 2对象图包含~10个模块中的数百个对象,由~5个组件使用),您希望做的是在每个测试中注入模拟。我找不到一个简单的方法来使用这种方法。是吗?@vasily我只在集成测试中使用这种方法,只模拟最大/最慢的依赖项(后端,应用层内)。对于单元测试,我将逻辑提取到单独的pojo(或几乎是pojo)类中,并为此类编写测试。在您的情况下,您的服务可以使用某种包含所有逻辑的帮助器。用一堆单元测试来覆盖这个助手。编写两个集成测试,以确保所有功能都能协同工作。在这一点上,您不需要涵盖所有情况。是的,这正是我现在所处的位置-我有针对“助手”和“经理”的单元测试,但我想对整个服务进行集成测试。然而,如果不模仿某些特定服务的依赖关系(至少我需要模仿Android的ConnectionManager、EventBus、ServerPingManager),我看不到编写集成测试的好方法。然而,我不想为所有测试模拟这些依赖关系——有些测试仍然应该使用非模拟依赖关系。无论如何,谢谢你的好主意+1.