public final class

Intents

extends java.lang.Object

 java.lang.Object

↳androidx.test.espresso.intent.Intents

Gradle dependencies

compile group: 'androidx.test.espresso', name: 'espresso-intents', version: '3.5.0-alpha06'

  • groupId: androidx.test.espresso
  • artifactId: espresso-intents
  • version: 3.5.0-alpha06

Artifact androidx.test.espresso:espresso-intents:3.5.0-alpha06 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.test.espresso:espresso-intents com.android.support.test.espresso:espresso-intents

Androidx class mapping:

androidx.test.espresso.intent.Intents android.support.test.espresso.intent.Intents

Overview

Intents enables validation and stubbing of intents sent out by the application under test.

An example test that simply validates an outgoing intent:

 public void testValidateIntentSentToPackage() {
   // User action that results in an external "phone" activity being launched.
   user.clickOnView(system.getView(R.id.callButton));

   // Using a canned RecordedIntentMatcher to validate that an intent resolving
   // to the "phone" activity has been sent.
   intended(toPackage("com.android.phone"));
 }
 

An example test with intent stubbing:

 public void testActivityResultIsHandledProperly() {
   // Build a result to return when a particular activity is launched.
   Intent resultData = new Intent();
   String phoneNumber = "123-345-6789";
   resultData.putExtra("phone", phoneNumber);
   ActivityResult result = new ActivityResult(Activity.RESULT_OK, resultData);

   // Set up result stubbing when an intent sent to "contacts" is seen.
   intending(toPackage("com.android.contacts")).respondWith(result));

   // User action that results in "contacts" activity being launched.
   // Launching activity expects phoneNumber to be returned and displays it on the screen.
   user.clickOnView(system.getView(R.id.pickButton));

   // Assert that data we set up above is shown.
   assertTrue(user.waitForText(phoneNumber));
 }
 

Summary

Methods
public static voidassertNoUnverifiedIntents()

Asserts that Intents does not have any unverified intents.

public static java.util.List<Intent>getIntents()

Returns the list of captured intents.

public static voidinit()

Initializes Intents and begins recording intents.

public static voidintended(<any> matcher)

Asserts that the given matcher matches one and only one intent sent by the application under test.

public static voidintended(<any> matcher, VerificationMode verificationMode)

Asserts that the given matcher matches a specified number of intents sent by the application under test.

public static OngoingStubbingintending(<any> matcher)

Enables stubbing intent responses.

public static voidrelease()

Clears Intents state.

public static VerificationModetimes(int times)

Allows verifying a specific number of intents sent by the application under test.

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

Methods

public static void init()

Initializes Intents and begins recording intents. Must be called prior to triggering any actions that send out intents which need to be verified or stubbed. This is similar to MockitoAnnotations.initMocks.

public static void release()

Clears Intents state. Must be called after each test case.

public static OngoingStubbing intending(<any> matcher)

Enables stubbing intent responses. This method is similar to Mockito.when and is particularly useful when the activity launching the intent expects data to be returned (and especially in the case when the destination activity is external). In this case, the test author can call intending(matcher).thenRespond(myResponse) and validate that the launching activity handles the result correctly. Note: the destination activity will not be launched.

Parameters:

matcher: the that matches intents for which stubbed response should be provided

Returns:

OngoingStubbing object to set stubbed response

public static void intended(<any> matcher)

Asserts that the given matcher matches one and only one intent sent by the application under test. This is an equivalent of verify(mock, times(1)) in Mockito. Verification does not have to occur in the same order as the intents were sent. Intents are recorded from the time that Intents.init is called.

Parameters:

matcher: the to be applied to captured intents

public static void intended(<any> matcher, VerificationMode verificationMode)

Asserts that the given matcher matches a specified number of intents sent by the application under test. This is an equivalent of verify(mock, times(num)) in Mockito. Verification does not have to occur in the same order as the intents were sent. Intents are recorded from the time that Intents.init is called.

Parameters:

matcher: the to be applied to captured intents

public static java.util.List<Intent> getIntents()

Returns the list of captured intents. Intents are recorded from the time that is called.

Callers can then verify the list of captured intents using their choice of assertion framework, such as truth.

public static VerificationMode times(int times)

Allows verifying a specific number of intents sent by the application under test. This is an equivalent of times(num) in Mockito.

Parameters:

times: the number of times that the intent should be matched.

public static void assertNoUnverifiedIntents()

Asserts that Intents does not have any unverified intents. You can use this method after you have verified your intents to make sure that nothing unexpected was sent out. This is an equivalent of verifyNoMoreInteractions() in Mockito.

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.espresso.intent;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.intent.Checks.checkNotNull;
import static androidx.test.espresso.intent.Checks.checkState;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;

import android.app.Instrumentation;
import android.content.Intent;
import android.view.View;
import androidx.test.annotation.ExperimentalTestApi;
import androidx.test.espresso.NoMatchingViewException;
import androidx.test.espresso.ViewAssertion;
import androidx.test.espresso.intent.matcher.IntentMatchers;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.intent.IntentCallback;
import androidx.test.runner.intent.IntentMonitor;
import androidx.test.runner.intent.IntentMonitorRegistry;
import androidx.test.runner.intent.IntentStubberRegistry;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitor;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import androidx.test.runner.lifecycle.Stage;
import com.google.errorprone.annotations.CheckReturnValue;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import org.hamcrest.Matcher;

/**
 * Intents enables validation and stubbing of intents sent out by the application under test.
 *
 * <p>An example test that simply validates an outgoing intent:
 *
 * <p><code>
 * <pre>
 * public void testValidateIntentSentToPackage() {
 *   // User action that results in an external "phone" activity being launched.
 *   user.clickOnView(system.getView(R.id.callButton));
 *
 *   // Using a canned RecordedIntentMatcher to validate that an intent resolving
 *   // to the "phone" activity has been sent.
 *   intended(toPackage("com.android.phone"));
 * }
 * </pre>
 * </code>
 *
 * <p>An example test with intent stubbing:
 *
 * <p><code>
 * <pre>
 * public void testActivityResultIsHandledProperly() {
 *   // Build a result to return when a particular activity is launched.
 *   Intent resultData = new Intent();
 *   String phoneNumber = "123-345-6789";
 *   resultData.putExtra("phone", phoneNumber);
 *   ActivityResult result = new ActivityResult(Activity.RESULT_OK, resultData);
 *
 *   // Set up result stubbing when an intent sent to "contacts" is seen.
 *   intending(toPackage("com.android.contacts")).respondWith(result));
 *
 *   // User action that results in "contacts" activity being launched.
 *   // Launching activity expects phoneNumber to be returned and displays it on the screen.
 *   user.clickOnView(system.getView(R.id.pickButton));
 *
 *   // Assert that data we set up above is shown.
 *   assertTrue(user.waitForText(phoneNumber));
 * }
 * </pre>
 * </code>
 */
public final class Intents {
  private static Intents defaultInstance;

  // Should be accessed only from main thread
  private static final List<VerifiableIntent> recordedIntents = new ArrayList<VerifiableIntent>();

  private static boolean isInitialized = false;

  private final ResettingStubber resettingStubber;
  private final IntentCallback intentCallback =
      new IntentCallback() {
        @Override
        public void onIntentSent(Intent intent) {
          recordedIntents.add(
              new VerifiableIntentImpl(
                  ((ResettingStubberImpl) resettingStubber).resolveIntent(intent)));
        }
      };
  private IntentMonitor intentMonitor;
  private static Instrumentation instrumentation;

  // VisibleForTesting
  Intents(ResettingStubber resettingStubber) {
    this.resettingStubber = checkNotNull(resettingStubber);
  }

  ////////////////////////////////////////////////////////////////////////////////////////////////
  // Static methods that delegate to internal* methods for implementation. This pattern is used to
  // enable unit testing, while preserving the convenience of static methods for the user.
  ////////////////////////////////////////////////////////////////////////////////////////////////

  /**
   * Initializes Intents and begins recording intents. Must be called prior to triggering any
   * actions that send out intents which need to be verified or stubbed. This is similar to
   * MockitoAnnotations.initMocks.
   */
  public static void init() {
    if (!IntentStubberRegistry.isLoaded()) {
      ResettingStubber resettingStubber = new ResettingStubberImpl();
      IntentStubberRegistry.load(resettingStubber);
      defaultInstance = new Intents(resettingStubber);
    }
    defaultInstance.internalInit();
  }

  /** Clears Intents state. Must be called after each test case. */
  public static void release() {
    if (defaultInstance != null) {
      defaultInstance.internalRelease();
    }
  }

  /**
   * Enables stubbing intent responses. This method is similar to Mockito.when and is particularly
   * useful when the activity launching the intent expects data to be returned (and especially in
   * the case when the destination activity is external). In this case, the test author can call
   * intending(matcher).thenRespond(myResponse) and validate that the launching activity handles the
   * result correctly. <b>Note:</b> the destination activity will not be launched.
   *
   * @param matcher the {@link Matcher} that matches intents for which stubbed response should be
   *     provided
   * @return {@link OngoingStubbing} object to set stubbed response
   */
  @CheckReturnValue
  public static OngoingStubbing intending(Matcher<Intent> matcher) {
    return checkNotNull(defaultInstance, "Intents not initialized. Did you forget to call init()?")
        .internalIntending(matcher);
  }

  /**
   * Asserts that the given matcher matches one and only one intent sent by the application under
   * test. This is an equivalent of verify(mock, times(1)) in Mockito. Verification does not have to
   * occur in the same order as the intents were sent. Intents are recorded from the time that
   * Intents.init is called.
   *
   * @param matcher the {@link Matcher} to be applied to captured intents
   * @throws AssertionFailedError if the given {@link Matcher} did not match any or matched more
   *     than one of the recorded intents
   */
  public static void intended(Matcher<Intent> matcher) {
    intended(matcher, times(1));
  }

  /**
   * Asserts that the given matcher matches a specified number of intents sent by the application
   * under test. This is an equivalent of verify(mock, times(num)) in Mockito. Verification does not
   * have to occur in the same order as the intents were sent. Intents are recorded from the time
   * that Intents.init is called.
   *
   * @param matcher the {@link Matcher} to be applied to captured intents
   * @throws AssertionFailedError if the given {@link Matcher} did not match the expected number of
   *     recorded intents
   */
  public static void intended(
      final Matcher<Intent> matcher, final VerificationMode verificationMode) {
    checkNotNull(defaultInstance, "Intents not initialized. Did you forget to call init()?");
    Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
    instrumentation.waitForIdleSync();
    if (resumedActivitiesExist(instrumentation)) {
      // Running through Espresso to take advantage of its synchronization mechanism.
      onView(isRoot())
          .check(
              new ViewAssertion() {
                @Override
                public void check(View view, NoMatchingViewException noViewFoundException) {
                  defaultInstance.internalIntended(matcher, verificationMode, recordedIntents);
                }
              });
    } else {
      // No activities are resumed, so we don't need Espresso's synchronization.
      PropogatingRunnable intendedRunnable =
          new PropogatingRunnable(
              new Runnable() {
                @Override
                public void run() {
                  defaultInstance.internalIntended(matcher, verificationMode, recordedIntents);
                }
              });
      instrumentation.runOnMainSync(intendedRunnable);
      instrumentation.waitForIdleSync();
      intendedRunnable.checkException();
    }
  }

  private static final class PropogatingRunnable implements Runnable {
    private final Runnable delegate;
    private AssertionError exception;

    public PropogatingRunnable(Runnable theRunnable) {
      this.delegate = theRunnable;
    }

    @Override
    public final void run() {
      try {
        delegate.run();
      } catch (AssertionError e) {
        exception = e;
      }
    }

    public void checkException() {
      if (exception != null) {
        throw exception;
      }
    }
  }

  /**
   * Returns the list of captured intents. Intents are recorded from the time that {@link
   * Intents.init} is called.
   *
   * <p>Callers can then verify the list of captured intents using their choice of assertion
   * framework, such as <a href="http://google.github.io/truth">truth</a>.
   */
  @ExperimentalTestApi
  public static List<Intent> getIntents() {
    final FutureTask<List<Intent>> getIntents =
        new FutureTask<>(
            new Callable<List<Intent>>() {
              @Override
              public List<Intent> call() throws Exception {
                List<Intent> intents = new ArrayList<>();
                for (VerifiableIntent verifiableIntent : recordedIntents) {
                  intents.add(verifiableIntent.getIntent());
                }
                return intents;
              }
            });
    instrumentation.runOnMainSync(getIntents);
    try {
      return getIntents.get();
    } catch (InterruptedException | ExecutionException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Allows verifying a specific number of intents sent by the application under test. This is an
   * equivalent of times(num) in Mockito.
   *
   * @param times the number of times that the intent should be matched.
   */
  public static VerificationMode times(int times) {
    if (times < 0) {
      throw new IllegalArgumentException("times expects a nonnegative integer");
    }
    return VerificationModes.times(times);
  }

  /**
   * Asserts that Intents does not have any unverified intents. You can use this method after you
   * have verified your intents to make sure that nothing unexpected was sent out. This is an
   * equivalent of verifyNoMoreInteractions() in Mockito.
   */
  public static void assertNoUnverifiedIntents() {
    intended(IntentMatchers.anyIntent(), VerificationModes.noUnverifiedIntents());
  }

  private static boolean resumedActivitiesExist(Instrumentation instrumentation) {
    final FutureTask<Boolean> checkResumed =
        new FutureTask<Boolean>(
            new Callable<Boolean>() {
              @Override
              public Boolean call() throws Exception {
                ActivityLifecycleMonitor monitor = ActivityLifecycleMonitorRegistry.getInstance();
                return !monitor.getActivitiesInStage(Stage.RESUMED).isEmpty();
              }
            });
    instrumentation.runOnMainSync(checkResumed);
    try {
      return checkResumed.get();
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    } catch (ExecutionException e) {
      throw new RuntimeException(e);
    }
  }

  ////////////////////////////////////////////////////////////////////////////////////////////////
  // Internal impl methods. Used for unit testing (hence package private)
  ////////////////////////////////////////////////////////////////////////////////////////////////

  void internalInit() {
    // TODO: Get rid of this or will we need any init here later?
    checkState(
        !isInitialized,
        "#init was called twice in a row. Make sure to call #release after every #init");
    instrumentation = InstrumentationRegistry.getInstrumentation();
    intentMonitor = IntentMonitorRegistry.getInstance();
    intentMonitor.addIntentCallback(intentCallback);
    resettingStubber.initialize();
    isInitialized = true;
  }

  OngoingStubbing internalIntending(Matcher<Intent> matcher) {
    return new OngoingStubbing(matcher, resettingStubber, instrumentation);
  }

  void internalRelease() {
    checkState(isInitialized, "init() must be called prior to using this method.");
    intentMonitor.removeIntentCallback(intentCallback);
    IntentStubberRegistry.reset();
    instrumentation.runOnMainSync(
        new Runnable() {
          @Override
          public void run() {
            recordedIntents.clear();
            resettingStubber.reset();
          }
        });
    isInitialized = false;
  }

  void internalIntended(
      Matcher<Intent> matcher, VerificationMode verificationMode, List<VerifiableIntent> intents) {
    checkState(isInitialized, "init() must be called prior to using this method.");
    verificationMode.verify(matcher, intents);
  }
}