public class

ServiceTestRule

extends java.lang.Object

 java.lang.Object

↳androidx.test.rule.ServiceTestRule

Gradle dependencies

compile group: 'androidx.test', name: 'rules', version: '1.4.1-alpha06'

  • groupId: androidx.test
  • artifactId: rules
  • version: 1.4.1-alpha06

Artifact androidx.test:rules:1.4.1-alpha06 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.test:rules com.android.support.test:rules

Androidx class mapping:

androidx.test.rule.ServiceTestRule android.support.test.rule.ServiceTestRule

Overview

A JUnit rule that provides a simplified mechanism to start and shutdown your service before and after the duration of your test. It also guarantees that the service is successfully connected when starting (or binding to) a service. The service can be started (or bound) using one of the helper methods. It will automatically be stopped (or unbound) after the test completes and any methods annotated with After are finished.

Note: This rule doesn't support because it's automatically destroyed when finishes all outstanding commands. So there is no guarantee to establish a successful connection in a timely manner.

Usage:

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

 @Test
 public void testWithStartedService() {
     mServiceRule.startService(
         new Intent(InstrumentationRegistry.getTargetContext(), MyService.class));
     //do something
 }

 @Test
 public void testWithBoundService() {
     IBinder binder = mServiceRule.bindService(
         new Intent(InstrumentationRegistry.getTargetContext(), MyService.class));
     MyService service = ((MyService.LocalBinder) binder).getService();
     assertTrue("True wasn't returned", service.doSomethingToReturnTrue());
 }
 

This API is currently in beta.

Summary

Constructors
publicServiceTestRule()

Creates a ServiceTestRule with a default timeout of 5 seconds

protectedServiceTestRule(long timeout, java.util.concurrent.TimeUnit timeUnit)

Methods
protected voidafterService()

Override this method to do your own service specific clean up after the service is shutdown.

public Statementapply(Statement base, Description description)

protected voidbeforeService()

Override this method to do your own service specific initialization before starting or binding to the service.

public IBinderbindService(Intent intent)

Works just like ServiceTestRule except uses an internal to guarantee successful bound.

public IBinderbindService(Intent intent, ServiceConnection connection, int flags)

Binds the service under test, in the same way as if it were started by with an that identifies a service.

public voidstartService(Intent intent)

Starts the service under test and blocks until the service is connected, in the same way as if it were started by with an that identifies a service.

public voidunbindService()

Unbinds the service under test that was previously bound by a call to ServiceTestRule or ServiceTestRule.

public static ServiceTestRulewithTimeout(long timeout, java.util.concurrent.TimeUnit timeUnit)

Factory method to create a ServiceTestRule with a custom timeout

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public ServiceTestRule()

Creates a ServiceTestRule with a default timeout of 5 seconds

protected ServiceTestRule(long timeout, java.util.concurrent.TimeUnit timeUnit)

Methods

public static ServiceTestRule withTimeout(long timeout, java.util.concurrent.TimeUnit timeUnit)

Factory method to create a ServiceTestRule with a custom timeout

Parameters:

timeout: the amount of time to wait for a service to connect.
timeUnit: the time unit representing how the timeout parameter should be interpreted

Returns:

a ServiceTestRule with the desired timeout

public void startService(Intent intent)

Starts the service under test and blocks until the service is connected, in the same way as if it were started by with an that identifies a service. If you use this method to start the service, it is automatically stopped at the end of the test run. However, it also attempts to bind to the service and waits for to be called to ensure successful connection.

Note: This method only supports services that allow clients to bind to them. In other words, if your service method returns null then a java.util.concurrent.TimeoutException will be thrown.

Parameters:

intent: An Intent that identifies a service, of the same form as the Intent passed to .

public IBinder bindService(Intent intent)

Works just like ServiceTestRule except uses an internal to guarantee successful bound. The operation option flag defaults to

See also: ServiceTestRule

public IBinder bindService(Intent intent, ServiceConnection connection, int flags)

Binds the service under test, in the same way as if it were started by with an that identifies a service. However, it waits for to be called before returning.

Parameters:

intent: Identifies the service to connect to. The Intent may specify either an explicit component name, or a logical description (action, category, etc) to match an published by a service.
connection: Receives information as the service is started and stopped. This must be a valid ServiceConnection object; it must not be null.
flags: Operation options for the binding. May be 0, , , , , , or .

Returns:

An object whose type is a subclass of IBinder, for making further calls into the service.

See also:

public void unbindService()

Unbinds the service under test that was previously bound by a call to ServiceTestRule or ServiceTestRule. You normally do not need to call this method since your service will automatically be stopped and unbound at the end of each test method.

protected void beforeService()

Override this method to do your own service specific initialization before starting or binding to the service. The method is called before each test method is executed including any method annotated with Before. Do not start or bind to a service from here!

protected void afterService()

Override this method to do your own service specific clean up after the service is shutdown. The method is called after each test method is executed including any method annotated with After and after necessary calls to stop (or unbind) the service under test were called.

public Statement apply(Statement base, Description description)

Source

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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 androidx.test.rule;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.test.annotation.ExperimentalTestApi;
import androidx.test.internal.util.Checks;
import androidx.test.platform.app.InstrumentationRegistry;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

/**
 * A JUnit rule that provides a simplified mechanism to start and shutdown your service before and
 * after the duration of your test. It also guarantees that the service is successfully connected
 * when starting (or binding to) a service. The service can be started (or bound) using one of the
 * helper methods. It will automatically be stopped (or unbound) after the test completes and any
 * methods annotated with <a href="http://junit.sourceforge.net/javadoc/org/junit/After.html"><code>
 * After</code></a> are finished.
 *
 * <p>Note: This rule doesn't support {@link android.app.IntentService} because it's automatically
 * destroyed when {@link android.app.IntentService#onHandleIntent(android.content.Intent)} finishes
 * all outstanding commands. So there is no guarantee to establish a successful connection in a
 * timely manner.
 *
 * <p>Usage:
 *
 * <pre>
 * &#064;Rule
 * public final ServiceTestRule mServiceRule = new ServiceTestRule();
 *
 * &#064;Test
 * public void testWithStartedService() {
 *     mServiceRule.startService(
 *         new Intent(InstrumentationRegistry.getTargetContext(), MyService.class));
 *     //do something
 * }
 *
 * &#064;Test
 * public void testWithBoundService() {
 *     IBinder binder = mServiceRule.bindService(
 *         new Intent(InstrumentationRegistry.getTargetContext(), MyService.class));
 *     MyService service = ((MyService.LocalBinder) binder).getService();
 *     assertTrue("True wasn't returned", service.doSomethingToReturnTrue());
 * }
 * </pre>
 *
 * <p>
 *
 * <p><b>This API is currently in beta.</b>
 */
@ExperimentalTestApi
public class ServiceTestRule implements TestRule {

  private static final String TAG = "ServiceTestRule";
  private static final long DEFAULT_TIMEOUT = 5L; // seconds

  private IBinder binder;
  private Intent serviceIntent;
  private ServiceConnection serviceConn;
  private final long timeout;
  private final TimeUnit timeUnit;

  boolean serviceStarted = false;
  boolean serviceBound = false;

  /** Creates a {@link ServiceTestRule} with a default timeout of 5 seconds */
  public ServiceTestRule() {
    this(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
  }

  protected ServiceTestRule(long timeout, TimeUnit timeUnit) {
    this.timeout = timeout;
    this.timeUnit = timeUnit;
  }

  /**
   * Factory method to create a {@link ServiceTestRule} with a custom timeout
   *
   * @param timeout the amount of time to wait for a service to connect.
   * @param timeUnit the time unit representing how the timeout parameter should be interpreted
   * @return a {@link ServiceTestRule} with the desired timeout
   */
  public static ServiceTestRule withTimeout(long timeout, TimeUnit timeUnit) {
    return new ServiceTestRule(timeout, timeUnit);
  }

  /**
   * Starts the service under test and blocks until the service is connected, in the same way as if
   * it were started by {@link android.content.Context#startService(Intent)
   * Context.startService(Intent)} with an {@link android.content.Intent} that identifies a service.
   * If you use this method to start the service, it is automatically stopped at the end of the test
   * run. However, it also attempts to bind to the service and waits for {@link
   * ServiceConnection#onServiceConnected(android.content.ComponentName, android.os.IBinder)} to be
   * called to ensure successful connection.
   *
   * <p><b>Note:</b> This method only supports services that allow clients to bind to them. In other
   * words, if your service {@link android.app.Service#onBind(Intent)} method returns {@code null}
   * then a {@link TimeoutException} will be thrown.
   *
   * @param intent An Intent that identifies a service, of the same form as the Intent passed to
   *     {@link android.content.Context#startService(Intent) Context.startService (Intent)}.
   * @throws SecurityException if you do not have permission to bind to the given service.
   * @throws TimeoutException if timed out waiting for a successful connection with the service.
   */
  public void startService(@NonNull Intent intent) throws TimeoutException {
    serviceIntent = Checks.checkNotNull(intent, "intent can't be null");
    InstrumentationRegistry.getInstrumentation().getTargetContext().startService(serviceIntent);
    serviceStarted = true;

    // bind to the started service to guarantee its started and connected before test execution
    serviceBound = bindServiceAndWait(serviceIntent, null, Context.BIND_AUTO_CREATE);
  }

  /**
   * Works just like {@link #bindService(android.content.Intent, android.content.ServiceConnection,
   * int)} except uses an internal {@link android.content.ServiceConnection} to guarantee successful
   * bound. The operation option flag defaults to {@link android.content.Context#BIND_AUTO_CREATE}
   *
   * @see #bindService(android.content.Intent, android.content.ServiceConnection, int)
   */
  public IBinder bindService(@NonNull Intent intent) throws TimeoutException {
    // no extras are expected by unbind
    serviceIntent = Checks.checkNotNull(intent, "intent can't be null").cloneFilter();
    serviceBound = bindServiceAndWait(intent, null, Context.BIND_AUTO_CREATE);
    return binder;
  }

  /**
   * Binds the service under test, in the same way as if it were started by {@link
   * android.content.Context#bindService(Intent, ServiceConnection, int) Context.bindService(Intent,
   * ServiceConnection, flags)} with an {@link android.content.Intent} that identifies a service.
   * However, it waits for {@link
   * ServiceConnection#onServiceConnected(android.content.ComponentName, android.os.IBinder)} to be
   * called before returning.
   *
   * @param intent Identifies the service to connect to. The Intent may specify either an explicit
   *     component name, or a logical description (action, category, etc) to match an {@link
   *     android.content.IntentFilter} published by a service.
   * @param connection Receives information as the service is started and stopped. This must be a
   *     valid ServiceConnection object; it must not be null.
   * @param flags Operation options for the binding. May be 0, {@link
   *     android.content.Context#BIND_AUTO_CREATE}, {@link
   *     android.content.Context#BIND_DEBUG_UNBIND}, {@link
   *     android.content.Context#BIND_NOT_FOREGROUND}, {@link
   *     android.content.Context#BIND_ABOVE_CLIENT}, {@link
   *     android.content.Context#BIND_ALLOW_OOM_MANAGEMENT}, or {@link
   *     android.content.Context#BIND_WAIVE_PRIORITY}.
   * @return An object whose type is a subclass of IBinder, for making further calls into the
   *     service.
   * @throws SecurityException if the called doesn't have permission to bind to the given service.
   * @throws TimeoutException if timed out waiting for a successful connection with the service.
   * @see android.content.Context#BIND_AUTO_CREATE
   * @see android.content.Context#BIND_DEBUG_UNBIND
   * @see android.content.Context#BIND_NOT_FOREGROUND
   */
  public IBinder bindService(
      @NonNull Intent intent, @NonNull ServiceConnection connection, int flags)
      throws TimeoutException {
    // no extras are expected by unbind
    serviceIntent = Checks.checkNotNull(intent, "intent can't be null").cloneFilter();
    ServiceConnection c = Checks.checkNotNull(connection, "connection can't be null");
    serviceBound = bindServiceAndWait(serviceIntent, c, flags);

    return binder;
  }

  @VisibleForTesting
  boolean bindServiceAndWait(Intent intent, final ServiceConnection conn, int flags)
      throws TimeoutException {

    ProxyServiceConnection serviceConn = new ProxyServiceConnection(conn);

    boolean isBound =
        InstrumentationRegistry.getInstrumentation()
            .getTargetContext()
            .bindService(intent, serviceConn, flags);

    if (isBound) {
      // block until service connection is established
      waitOnLatch(serviceConn.connectedLatch, "connected");
      this.serviceConn = serviceConn;
    } else {
      Log.e(TAG, "Failed to bind to service! Is your service declared in the manifest?");
    }

    return isBound;
  }

  /**
   * Unbinds the service under test that was previously bound by a call to {@link
   * #bindService(android.content.Intent)} or {@link #bindService(android.content.Intent,
   * android.content.ServiceConnection, int)}. You normally do not need to call this method since
   * your service will automatically be stopped and unbound at the end of each test method.
   */
  public void unbindService() {
    if (serviceBound) {
      InstrumentationRegistry.getInstrumentation().getTargetContext().unbindService(serviceConn);
      binder = null;
      serviceBound = false;
    }
  }

  /**
   * This class is used to wait until a successful connection to the service was established. It
   * then serves as a proxy to original {@link android.content.ServiceConnection} passed by the
   * caller.
   */
  class ProxyServiceConnection implements ServiceConnection {
    private ServiceConnection callerConnection;
    public CountDownLatch connectedLatch = new CountDownLatch(1);

    private ProxyServiceConnection(ServiceConnection connection) {
      callerConnection = connection;
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
      // store the service binder to return to the caller
      binder = service;
      if (callerConnection != null) {
        // pass through everything to the callers ServiceConnection
        callerConnection.onServiceConnected(name, service);
      }
      connectedLatch.countDown();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
      // The process hosting the service has crashed or been killed.
      Log.e(TAG, "Connection to the Service has been lost!");
      binder = null;
      if (callerConnection != null) {
        // pass through everything to the callers ServiceConnection
        callerConnection.onServiceDisconnected(name);
      }
    }
  }

  /** Helper method to block on a given latch for the duration of the set timeout */
  private void waitOnLatch(CountDownLatch latch, String actionName) throws TimeoutException {
    try {
      if (!latch.await(timeout, timeUnit)) {
        throw new TimeoutException(
            "Waited for "
                + timeout
                + " "
                + timeUnit.name()
                + ","
                + " but service was never "
                + actionName);
      }
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new RuntimeException("Interrupted while waiting for service to be " + actionName, e);
    }
  }

  /**
   * Makes the necessary calls to stop (or unbind) the service under test. This method is called
   * automatically called after test execution. This is not a blocking call since there is no
   * reliable way to guarantee successful disconnect without access to service lifecycle.
   */
  @VisibleForTesting
  void shutdownService() throws TimeoutException {
    if (serviceStarted) {
      InstrumentationRegistry.getInstrumentation().getTargetContext().stopService(serviceIntent);
      serviceStarted = false;
    }
    unbindService();
  }

  /**
   * Override this method to do your own service specific initialization before starting or binding
   * to the service. The method is called before each test method is executed including any method
   * annotated with <a href="http://junit.sourceforge.net/javadoc/org/junit/Before.html"><code>
   * Before</code></a>. Do not start or bind to a service from here!
   */
  protected void beforeService() {
    // empty by default
  }

  /**
   * Override this method to do your own service specific clean up after the service is shutdown.
   * The method is called after each test method is executed including any method annotated with <a
   * href="http://junit.sourceforge.net/javadoc/org/junit/After.html"><code>After</code></a> and
   * after necessary calls to stop (or unbind) the service under test were called.
   */
  protected void afterService() {
    // empty by default
  }

  @Override
  public Statement apply(final Statement base, Description description) {
    return new ServiceStatement(base);
  }

  /**
   * {@link Statement} that executes the service lifecycle methods before and after the execution of
   * the test.
   */
  private class ServiceStatement extends Statement {
    private final Statement base;

    public ServiceStatement(Statement base) {
      this.base = base;
    }

    @Override
    public void evaluate() throws Throwable {
      try {
        beforeService();
        base.evaluate();
      } finally {
        shutdownService();
        afterService();
      }
    }
  }
}