Showing posts with label testing. Show all posts
Showing posts with label testing. Show all posts

Monday, November 13, 2017

Types of Android Tests

I saw this Reddit discussion on Android testing concepts, which tries to categorize tests into short, medium and long. I don't find that a very useful way to think about different types of tests, so I thought I'd give offer my view.

Two dimensions

I think it's more useful to think of tests in two dimensions

  • Does it run on the JVM or needs a device?
  • Does it test the UI or not?

Depending on the answer to these two questions you get 4 types of tests.

JVM tests

JVM tests are the faster to run since you don't need to deploy to a device (physical or emulator), and should be preferred. Try to structure your app so that the logic is in pure Java classes i.e. does not use the Android framework.

Since we are writing Android apps, it is difficult to test the UI without the Android framework. There are two ways you can do that.

Robolectric

The first way to test the UI on the JVM is Robolectric, which mocks the Android framework. I don't recommend that, because the mocked classes don't necessarily reflect the actual behavior on the Android framework.

Model-View-Presenter (MVP)

The second way is to make your Android classes as logic-free as possible. There are various architecture patterns you can use to achieve that, for an example Model-View-Presenter (MVP). MVP allows you to encapsulate the Android part inside the View (Activity or Fragment) and extract the logic into the Presenter, which does not use any Android framework code. This way, you can test the Presenter on the JVM.

Espresso

After you have tested your logic extensively on the JVM (you may want to aim for 100% test coverage), you should add some UI tests. Think of these as sanity checks, going through the happy path to make sure the app does not crash when you bump up the library version. I use Espresso for UI tests, together with Mockito and MockWebServer to set up a hermetic environment for repeatable tests.

More info

Thursday, June 2, 2016

Advanced Espresso at #io16

I missed the Advanced Espresso talk at Google I/O this year because it was at the same time as Building rich fitness experiences with Google Fit platform and Android Wear, where Fit Cat was featured.

Finally got a chance to watch it at home, and wow, lots of good tips and tricks!

onTransitionToIdle (at 10:33)

According to the talk, it's preferable to set the state of the idling resource once, and call the idle transition callback when the state changes to idle. A good example is CountingIdlingResource, in the decrement function.

I went back to look at my custom idling resources, and try to skip calling the idle transition callback in isIdleNow. That gives me this error:

Caused by: java.lang.IllegalStateException: Resource com.sqisland.espresso.idling_resource.intent_service.IntentServiceIdlingResource isIdleNow() is returning true, but a message indicating that the resource has transitioned from busy to idle was never sent.

So it's seems like I must call the callback. From the doc, it seems harmless to call it multiple times, so I'm going to leave my querying idling resources as is.

For idling resources that query the state in isIdleNow, there is no need to call the idle transition callback. See IntentServiceIdlingResource for an example.

loopMainThreadForAtLeast (at 11:18)

Try to avoid it, but if you need some time to pass in your test, do not call Thread.sleep or SystemClock.sleep. Instead, use UiController.loopMainThreadForAtLeast.

You will need to make a custom ViewAction to get access to the UiController. Note: Not to store the UIController outside the ViewAction, because it may not be valid any more.

More tips

  • (12:10) Do not copy and paste between tests. Create helper functions.
  • (13:35) Use available Matchers. Write your own only if necessary, with TypeSafeMatcher and BoundedMatcher.
  • (15:08) Use CountingIdlingResource.
  • (15:54) Change IdlingPolicy if the defaults are too short.
  • (16:35) Focus on testing behavior, not layout properties. Use position assertions e.g. isBelow if you must verify layout. They are relative and easier to maintain.
  • (17:35) Write many small tests rather than large ones.
  • (18:44) Launch directly into desired screen and state with intents.
  • (19:56) Most UI tests should be as hermetic as possible.
  • (20:37) Use Espresso Intents to intercept Intents that launch external apps.
  • (21:19) Learn how to run long running animations. Read ANIMATOR_DURATION_SCALE from system settings and disable that in your app when it is zero. Show surface updates from Developer Tools to see hidden animations.
  • (24:00) Implement your own FailureHandler if you want to log extra information when your test fails.
  • (24:41) Developer Tools FTW!
  • (25:05) Change touch and hold delay for slow devices / emulator
  • (25:49) Enable testing for accessibility issues. Super easy: AccessibilityValidator.enable()

Thank you Wojtek KaliciƄski for the informative talk!

Tuesday, January 26, 2016

Daggerless Dependency Injection for Testing

I have written many articles about using Dagger to inject mocks into tests to remove external dependencies. But many people don't want to use Dagger, for a variety of reasons.

Dependency injection is a design pattern. The libraries are there to help you, but if you prefer you can definitely implement it yourself.

Remember I said I want to write a book on Espresso, but only if there is enough demand? For this particular topic there is definitely a lot of demand, so I went ahead and wrote a primer: Daggerless Dependency Injection for Testing.

It shows you how to how to set up your Android project and implement dependency injection from first principles. You will be refactoring a sample app to learn how to extract dependencies for injection purposes. And of course there are some Espresso tests as well.

Get it here: https://gum.co/DaggerlessDITesting.

Thursday, December 31, 2015

Mock Application in Espresso for Dependency Injection

I read this great post by Artem Zinnatullin on How to mock dependencies in Unit, Integration and Functional tests; Dagger, Robolectric and Instrumentation. The part I like the best is to use a different application in tests to provide different dependencies, and I decided to try it with Espresso.

Mock application via custom test runner

My current approach to dependency injection is to expose a setComponent function in my application for tests to supply the test component, which is not great because ideally the application should not have test-specific code.

The new approach is to subclass the application in the androidTest folder and load it during tests via a custom test runner.

public class DemoApplication extends Application {
  private final DemoComponent component = createComponent();

  protected DemoComponent createComponent() {
    return DaggerDemoApplication_ApplicationComponent.builder()
        .clockModule(new ClockModule())
        .build();
  }

  public DemoComponent component() {
    return component;
  }
}

In the application we initialize a DemoComponent with createComponent() and stash it in a final variable to be used later.

public class MockDemoApplication extends DemoApplication {
  @Override
  protected DemoComponent createComponent() {
    return DaggerMainActivityTest_TestComponent.builder()
        .mockClockModule(new MockClockModule())
        .build();
  }
}

For testing, we subclass our application and override createComponent to supply the test component instead.

To use the mock application in tests, we need a custom test runner:

public class MockTestRunner extends AndroidJUnitRunner {
  @Override
  public Application newApplication(
      ClassLoader cl, String className, Context context)
      throws InstantiationException, 
             IllegalAccessException, 
             ClassNotFoundException {
    return super.newApplication(
      cl, MockDemoApplication.class.getName(), context);
  }
}

We give it MockDemoApplication.class.getName() as the class name so the test runner will load the mock application instead of the main one.

Per application vs per test

This approach is slightly different from setComponent because we initialize the test component only once, rather than before each test method. Make sure you clear the state of your test modules before each test so run each test in from a clean slate.

Source code

I have updated two of my repositories to use this approach:

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Friday, November 6, 2015

MVP: The Missing Link

I have been wanting to learn the Model View Presenter pattern for a while, and was super excited to get Michael Cameron to give a talk on it at GDG Boulder last night.

View Interface

I have this vague notion that MVP is good for JUnit testing, and I finally figured out why. The secret, the missing link, is View Interface, which is not a part of the MVP acronym. View Interface defines the contract between the view and the presenter, allowing you to mock one while testing the other.

With View Interface, you can compartmentalize your Android UI code into the view (typically a Fragment or Activity), which communicates with your presenter solely through the View Interface. This separation allows you to mock your View Interface with zero Android code, which means you can test your presenter with JUnit on the JVM.

Mocking via build flavors

Mocking is essential for hermetic testing, but people are often intimated by dependency injection frameworks like Dagger. If you prefer, you can provide mocks to your test using build flavors. An excellent technique!

Mockito

Mockito is a great mocking framework with a fluent API. One very nice feature is mocking via annotations. To use that, annotate the fields you want to mock with @Mock, and initialize them all with MockitoAnnotations.initMocks() in your @Before function.

@Mock
private MyThing myThing;

@Mock
private OtherThing otherThing;

@Before
public void setUp() throws Exception {
  MockitoAnnotations.initMocks(this);
}

This is equivalent to:

private MyThing myThing;
private OtherThing otherThing;

@Before public void setUp() throws Exception {
  myThing = Mockito.mock(MyThing.class);
  otherThing = Mockito.mock(OtherThing.class);
}

Slides: http://www.slideshare.net/DarxVal/model-view-presenter-presentation

Codelab

Google has published the excellent Android Testing Codelab going through an app with the MVP architecture. You can see JUnit and Espresso testing in action, mocks via build flavors, and various other techniques. Go check it out!

Tuesday, October 6, 2015

Espresso: Save and restore state

Do you save and restore state of your activities, fragments and custom views? Do you test them?

One way to test saving and restoring state is to rotate the screen in your Espresso test.

private void rotateScreen() {
  Context context = InstrumentationRegistry.getTargetContext();
  int orientation 
    = context.getResources().getConfiguration().orientation;

  Activity activity = activityRule.getActivity();
  activity.setRequestedOrientation(
      (orientation == Configuration.ORIENTATION_PORTRAIT) ?
          ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE : 
          ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}

With this helper function, you can write tests like this:

@Test
public void incrementTwiceAndRotateScreen() {
  onView(withId(R.id.count))
      .check(matches(withText("0")));

  onView(withId(R.id.increment_button))
      .perform(click(), click());
  onView(withId(R.id.count))
      .check(matches(withText("2")));

  rotateScreen();

  onView(withId(R.id.count))
      .check(matches(withText("2")));
}

Source code

https://github.com/chiuki/espresso-samples/ under rotate-screen

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Tuesday, August 4, 2015

Testing SharedPreferences with Dagger and Espresso

Back in April I asked if people are interested in an Espresso book. The responses are very encouraging, but I am still intimidated by the idea of writing a whole book. Rather than wait until I muster enough courage (and time) to take on a book project, I decided to start with bite-sized courses.

The first course

Testing with SharedPreferences with Dagger and Espresso covers dependency injection with Dagger for testing, and walk through a sample app to use it to test SharedPreferences. The course includes 8 videos, plus the source code.

I hope you find it useful!

Tuesday, July 28, 2015

Espresso: Wait for dialog to dismiss

Did you complete the Espresso Idling Resource quiz? Here comes the solution!

DialogFragmentIdlingResource

In the quiz, the test fails because we were verifying the text before the loading dialog dismisses. To make it pass, we can add an idling resource that is only idle when the dialog is not shown.

public DialogFragmentIdlingResource(
    FragmentManager manager, String tag) {
  this.manager = manager;
  this.tag = tag;
}

@Override
public boolean isIdleNow() {
  boolean idle = (manager.findFragmentByTag(tag) == null);
  if (idle) {
    resourceCallback.onTransitionToIdle();
  }
  return idle;
}

The idea is to query the FragmentManager with the tag of the loading dialog. If it is null, the DialogFragment is not there, and we consider the app idle.

Using the idling resource in test

@Test
public void done() {
  IdlingResource idlingResource = new DialogFragmentIdlingResource(
      activityRule.getActivity().getSupportFragmentManager(),
      LoadingDialogFragment.TAG);

  Espresso.registerIdlingResources(idlingResource);

  onView(withId(R.id.text))
      .check(matches(withText(R.string.done)));

  Espresso.unregisterIdlingResources(idlingResource);
}

We create an DialogFragmentIdlingResource with the tag from the loading dialog so that it knows what to wait for. With that, the test will wait until the loading dialog dismisses before proceeding. And now the test passes!

Source code

https://github.com/chiuki/espresso-samples/ under idling-resource-dialog-fragment on the solution branch.

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Tuesday, July 14, 2015

Espresso: Idling Resource quiz

I have written two blog posts on Espresso custom idling resource:

And I am wondering: Is this enough? Do people understand? To figure that out, I made a quiz!

Exercise for the reader

I created a failing test, and your task is to make it pass by adding a custom idling resource. Ready? Here is the code:

https://github.com/chiuki/espresso-samples under idling-resource-dialog-fragment

Fork the repo and commit your answer there. Reply to this Google+ thread with a link to your repo.

The solution will be posted in two weeks.

Solution

Two weeks has passed, so here is the solution!

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Sunday, July 12, 2015

How to test a watch face?

Fit Cat is an Android Wear watch face that shows the time with a different cat depending on how many steps you walked that day. How do you test something like that? Walk a lot while looking at your watch all the time?

I wrote Fit Cat with a modular structure that allows both unit testing and instrumentation testing.

All Android Wear apps have two modules, mobile and wear. I made them both depend on a ui module, which has the implementation for rendering the bitmap for the watch face. This in turn depends on a lib module, which is a pure Java library.

Unit testing

The lib module contains the pure Java class ActionStore, which has the logic to determine which image to show depending on the time, steps and randomizer value:

  • The number of steps decides the action level, each mapping to multiple actions.
  • Within that level, an action is picked randomly.
  • Within that action, the time determines the image for the animation frame.

The watch face calls actionStore.getImage(time, steps, randomizerValue) to get the image file name. In the junit tests, various values of time, steps and randomizerValue are passed to that function, and the resulting image file names are verified.

Instrumentation testing

Besides verifying the logic, I also want to visually inspect the rendered bitmaps. The ui module has a Renderer class with a function drawWatchFace(canvas, steps, action, time, batteryPercent). I test this function by adding an InstrumentationTestCase to the mobile module, which enumerates all possible actions and write the animation frames to sdcard. I then combine them into animated gifs for visual inspection.

Since the wear module also uses this Renderer class from the ui module, I am confident that the layout for all the different animation frames are good. Plus, I get to see all the cute actions Fit Cat does!

Sunday, June 7, 2015

Espresso: Elapsed time

IdlingResource is a powerful concept in Espresso that is a little difficult to grasp. I have already written an example to wait for an IntentService to be idle, but I thought I'd write another example where it literally waits for time to pass.

Timer game

You start the timer, wait for a minute, and stop it. The game mocks you if you don't wait long enough:

If you wait for longer than a minute, the game congratulates you:

IdlingResource

To test this, we need to ask Espresso to wait. We could use SystemClock.sleep(60000), but that blocks the testing thread. Instead, we write a custom IdlingResource.

public ElapsedTimeIdlingResource(long waitingTime) {
  this.startTime = System.currentTimeMillis();
  this.waitingTime = waitingTime;
}

@Override
public boolean isIdleNow() {
  long elapsed = System.currentTimeMillis() - startTime;
  boolean idle = (elapsed >= waitingTime);
  if (idle) {
    resourceCallback.onTransitionToIdle();
  }
  return idle;
}

In the constructor, we save the current time, and also how long we need to wait. When Espresso wants to know if it should wait more, it calls isIdleNow(), which computes how much time has elapsed, and returns true if it is longer than the amount of time we need to wait.

Timeout policies

Besides the IdlingResource, we also need to change Espresso's timeout policies.

long waitingTime = DateUtils.SECOND_IN_MILLIS * 75;

IdlingPolicies.setMasterPolicyTimeout(
    waitingTime * 2, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(
    waitingTime * 2, TimeUnit.MILLISECONDS);

IdlingResource idlingResource 
    = new ElapsedTimeIdlingResource(waitingTime);
Espresso.registerIdlingResources(idlingResource);

By default, Espresso times out the app if it has been idle for 60 seconds, and times out an IdlingResource that has been idle for 26 seconds. We change the timeout policies to be twice our waiting time to make sure Espresso does not kill our test.

How does Espresso wait?

If you run this test, you will notice that the IdlingResource does not wait for the exact amount of time you specified, but slightly longer. Espresso waits by periodically checking if everything is idle, and your resource is only idle after the amount of time you specified, so it is guaranteed to wait for at least that long, but it could, and usually does, wait longer.

Full example

Check out the source code for a full example. Pay attention to see how the test cleans up by resetting the timeout policies and unregister the IdlingResource.

Source code: https://github.com/chiuki/espresso-samples/ under idling-resource-elapsed-time

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Friday, May 8, 2015

Espresso: Match Toolbar Title

How do check the value of the title bar in Espresso?

Let's take a look with Hierarchy Viewer. Remember to run it with ANDROID_HVPROTO=ddm if your device is not rooted.

First attempt: Match the TextView

We found our TextView in the hierarchy, but it does not have an id. However, we notice that it is a child of the Toolbar, so we can match it using withParent.

@Test
public void toolbarTitle() {
  CharSequence title = InstrumentationRegistry.getTargetContext()
    .getString(R.string.my_title);
  matchToolbarTitle(title);
}

private static ViewInteraction matchToolbarTitle(
    CharSequence title) {
  return onView(
      allOf(
          isAssignableFrom(TextView.class),
          withParent(isAssignableFrom(Toolbar.class))))
      .check(matches(withText(title.toString())));
}

This is how we look for the view to verify the title:

  • It is a TextView
  • It has a parent that is a Toolbar

This works, but it depends on the inner structure of Toolbar, which is not a part of the public API and may change in future versions. Let's make it better.

Second attempt: Custom matcher with Toolbar.getTitle()

private static ViewInteraction matchToolbarTitle(
    CharSequence title) {
  return onView(isAssignableFrom(Toolbar.class))
      .check(matches(withToolbarTitle(is(title))));
}

private static Matcher<Object> withToolbarTitle(
    final Matcher<CharSequence> textMatcher) {
  return new BoundedMatcher<Object, Toolbar>(Toolbar.class) {
    @Override public boolean matchesSafely(Toolbar toolbar) {
      return textMatcher.matches(toolbar.getTitle());
    }
    @Override public void describeTo(Description description) {
      description.appendText("with toolbar title: ");
      textMatcher.describeTo(description);
    }
  };
}

withToolbarTitle() returns a custom BoundedMatcher, which gives us type safety. In matchesSafely(), we call Toolbar.getTitle() to check its value.

To use this custom matcher, we change our helper function to look for the Toolbar itself, and check that it has the expected title. Notice that we pass is(title) to withToolbarTitle, which takes a text matcher rather than a string. That way we can use other matchers such as startsWith and endsWith.

Source code and notes

https://github.com/chiuki/espresso-samples/ under toolbar-title

  • The Toolbar is actually android.support.v7.widget.Toolbar because we are using AppCompat in the sample.
  • In both attempts, we assume that there is only one Toolbar. If you have more than one, you will need to have some extra matchers to pinpoint the one you want to match.
  • We use a helper function matchToolbarTitle() to hide the implementation details of how we match the toolbar title. That way, if the underlying code changes, we only need to update one place for all our tests that wants to match the toolbar title.
  • The helper function matchToolbarTitle() returns a ViewInteraction. This allows us to chain this call to verify other properties of the Toolbar. i.e. you can do something like this:
    matchToolbarTitle(title)
      .check(matches(isDisplayed()));
    

Article inspired by discussion with Danny Roa, Jacob Tabak and Jake Wharton.

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Saturday, April 25, 2015

Espresso: Custom Idling Resource

One key feature in Espresso is the synchronization between the test operations and the application being tested. This is built around the concept of idling: Espresso waits until the app is "idle" before performing the next action and checking the next assertion.

Idle

What does it mean for the app to be idle? Espresso checks for several conditions:

  • There is no UI events in the current message queue
  • There is no tasks in the default AsyncTask thread pool

This takes care of waiting for the UI rendering and AsyncTask completion. However, if your app performs long-running operations in other ways, Espresso will not know how to wait for those operations to finish. If that is the case, you can tell Espresso to wait by writing a custom IdlingResource.

IntentServiceIdlingResource

Say you use an IntentService to do some long computation, and return the result to your activity via broadcast. We want Espresso to wait until the result is returned before checking that it was displayed correctly.

To implement an IdlingResource, you need to override 3 functions: getName(), registerIdleTransitionCallback() and isIdleNow().

@Override
public String getName() {
  return IntentServiceIdlingResource.class.getName();
}

@Override
public void registerIdleTransitionCallback(
    ResourceCallback resourceCallback) {
  this.resourceCallback = resourceCallback;
}

@Override
public boolean isIdleNow() {
  boolean idle = !isIntentServiceRunning();
  if (idle && resourceCallback != null) {
    resourceCallback.onTransitionToIdle();
  }
  return idle;
}

private boolean isIntentServiceRunning() {
  ActivityManager manager = 
    (ActivityManager) context.getSystemService(
      Context.ACTIVITY_SERVICE);
  for (ActivityManager.RunningServiceInfo info : 
          manager.getRunningServices(Integer.MAX_VALUE)) {
    if (RepeatService.class.getName().equals(
          info.service.getClassName())) {
      return true;
    }
  }
  return false;
}

The idle logic is implemented in isIdleNow(). In our case, we check if our IntentService is running by querying the ActivityManager. If the IntentService is not running, we can inform Espresso calling resourceCallback.onTransitionToIdle().

Register your idling resource

You need to register your custom idling resource in order for Espresso to wait for it. Do it in a @Before method in your test, and unregister it in @After.

@Before
public void registerIntentServiceIdlingResource() {
  Instrumentation instrumentation 
    = InstrumentationRegistry.getInstrumentation();
  idlingResource = new IntentServiceIdlingResource(
    instrumentation.getTargetContext());
  Espresso.registerIdlingResources(idlingResource);
}

@After
public void unregisterIntentServiceIdlingResource() {
  Espresso.unregisterIdlingResources(idlingResource);
}

Full example

Check out the source code for a full example. Try commenting out the IdlingResource registration and watch the test fail.

Source code: https://github.com/chiuki/espresso-samples/ under idling-resource-intent-service

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Tuesday, April 21, 2015

Espresso 2.1: ActivityTestRule

When I wrote my android-test-demo app, I wanted to use Jake Wharton's ActivityRule, but couldn't because I wanted to customize the launch intent for each test method.

Today I got my solution from Google: Test rules in Espresso 2.1. ActivityTestRule can be configured to take a different launch intent per test method like this:

@Rule
public ActivityTestRule activityRule = new ActivityTestRule<>(
    MainActivity.class,
    true,    // initialTouchMode
    false);  // launchActivity. False to set intent per method

I then supply a launch intent in the test method:

@Test
public void intent() {
  DateTime dateTime = new DateTime(2014, 10, 15, 0, 0, 0);
  Intent intent = new Intent();
  intent.putExtra(MainActivity.KEY_MILLIS, dateTime.getMillis());

  activityRule.launchActivity(intent);

  onView(withId(R.id.date))
      .check(matches(withText("2014-10-15")));
}

See MainActivityTest.java for more details.

ActivityTestRule makes tests much cleaner. Thank you Google for making Espresso better and better!

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Thursday, April 9, 2015

Dagger 2 + Espresso 2 + Mockito

I've been doing Android instrumentation testing with Dagger, Espresso and Mockito, and I love it. To commemorate the launch of Dagger 2 out of SNAPSHOT, I am sharing a demo repo with Dagger 2, Espresso 2 and Mockito:

https://github.com/chiuki/android-test-demo

Dagger Components

Dependency injection allows our app and test obtain different modules. This is very useful for creating repeatable test cases. The demo app displays today's date in the format yyyy-MM-dd. We would like to test that against a known date, instead of depend on the actual date when we run the test.

In Dagger 2, a Component provides modules for your whole app, and defines where to inject them.

public interface DemoComponent {
  void inject(MainActivity mainActivity);
}
@Singleton
@Component(modules = ClockModule.class)
public interface ApplicationComponent extends DemoComponent {
}
@Singleton
@Component(modules = MockClockModule.class)
public interface TestComponent extends DemoComponent {
  void inject(MainActivityTest mainActivityTest);
}

ApplicationComponent is used when the app is run normally, and TestComponent is used during tests. Both components injects into MainActivity.

How does the MainActivity know which component to use? It injects via the application, which stores the component.

Approach 1: setComponent

private DemoComponent component = null;

@Override public void onCreate() {
  super.onCreate();
  if (component == null) {
    component = DaggerDemoApplication_ApplicationComponent
        .builder()
        .clockModule(new ClockModule())
        .build();
  }
}

public void setComponent(DemoComponent component) {
  this.component = component;
}

public DemoComponent component() {
  return component;
}

We call setComponent() in test, which runs before onCreate(), so the TestComponent is used. When the app is run normally, component will be set to ApplicationComponent in onCreate().

Approach 2: Mock application

Exposing setComponent in the application is not great because ideally the application should not have test-specific code. Another approach is to subclass the application in the androidTest folder and load it during tests via a custom test runner. See blog.sqisland.com/2015/12/mock-application-in-espresso.html for more details.

Mockito

The app has a Clock class which returns the current time.

public DateTime getNow() {
  return new DateTime();
}

TestComponent contains MockClockModule, which provides Clock as mocked by Mockito. This way MainActivityTest can supply a pre-determined date during test.

Mockito.when(clock.getNow())
  .thenReturn(new DateTime(2008, 9, 23, 0, 0, 0));

Since we have singleton modules, the same mocked Clock is supplied to the app. With that, it will display the provided date instead of today's date:

onView(withId(R.id.date))
  .check(matches(withText("2008-09-23")));

More

There is a lot more in the repo, including testing activity launch with intent and unit testing with JUnit. Please check it out:

https://github.com/chiuki/android-test-demo

Look at the setComponent branch to see the old code for injection using the setComponent function. Master uses mock application and custom test runner for injection.

Also, I am toying with the idea of writing a book on Espresso. Please take a look at the outline and fill in this form if you think I should write it!

Update: I have started publishing Espresso courses! https://gumroad.com/chiuki

Further reading: