Hot questions for Using Mockito in android espresso

Top 10 Java Open Source / Mockito / android espresso

Question:

I have an Android project that uses Mockito, Hamcrest and Espresso to help with testing.

No matter what I try with the Gradle build file, I get a NoSuchMethodError for org.hamcrest.Matcher.anyOf when I try to run my tests after doing gradle androidTestCompile.

Here is my current configuration:

dependencies {
    compile project(':GameCore')
    androidTestCompile files(
            'libs/espresso-1.1.jar',
            'libs/testrunner-1.1.jar',
            'libs/testrunner-runtime-1.1.jar'
    )
    androidTestCompile(
            'junit:junit:4.11',
            'org.mockito:mockito-core:1.10.0',
            'com.google.guava:guava:14.0.1',
            'com.squareup.dagger:dagger:1.1.0',
            'com.google.dexmaker:dexmaker:1.0',
            'com.google.dexmaker:dexmaker-mockito:1.0',
            'org.hamcrest:hamcrest-core:1.3',
            'org.hamcrest:hamcrest-library:1.3'
            )

I've tried rewriting the Mockito and JUnit requirements to exclude Hamcrest like so:

androidTestCompile('junit:junit:4.11') {
    exclude group: 'org.hamcrest'
}

But this doesn't make any difference.

The GameCore project is a pure Java project. It also has dependencies on JUnit and Mockito, but as they're specified as testCompile, I don't think they should be interfering.

The output for gradle dependencies for this module for 'androidTestCompile` is:

+--- junit:junit:4.11                                                               
|    \--- org.hamcrest:hamcrest-core:1.3
+--- org.mockito:mockito-core:1.10.0
|    +--- org.hamcrest:hamcrest-core:1.1 -> 1.3
|    \--- org.objenesis:objenesis:2.1
+--- com.google.guava:guava:14.0.1
+--- com.squareup.dagger:dagger:1.1.0
|    \--- javax.inject:javax.inject:1
+--- com.google.dexmaker:dexmaker:1.0
+--- com.google.dexmaker:dexmaker-mockito:1.0
|    +--- com.google.dexmaker:dexmaker:1.0
|    \--- org.mockito:mockito-core:1.9.5 -> 1.10.0 (*)
+--- org.hamcrest:hamcrest-core:1.3
\--- org.hamcrest:hamcrest-library:1.3
     \--- org.hamcrest:hamcrest-core:1.3

Edit

Having further investigated the problem, I see that espresso needs Hamcrest 1.1, but I'm also using assertThat, which is in Hamcrest 1.3. Hamcrest 1.3 doesn't have the anyOf method that espresso uses. So I guess I'm stuck :)


Answer:

I realised that assertThat is in Hamcrest 1.1, it's just in hamcrest-integration instead of hamcrest-core. I changed my build file and it's all working now:

androidTestCompile files(
        'libs/espresso-1.1.jar',
        'libs/testrunner-1.1.jar',
        'libs/testrunner-runtime-1.1.jar'
)
androidTestCompile(
        'org.mockito:mockito-core:1.9.5',
        'com.google.dexmaker:dexmaker-mockito:1.0',
        'com.google.dexmaker:dexmaker:1.0',
        'com.google.guava:guava:14.0.1',
        'com.squareup.dagger:dagger:1.1.0',
        'org.hamcrest:hamcrest-core:1.1',
        'org.hamcrest:hamcrest-integration:1.1',
        'org.hamcrest:hamcrest-library:1.1'
)

I tried using espresso-1.1-bundled.jar but that caused dex errors because two copies of Hamcrest 1.1 were pulled in, so I'd have had to exclude it from a bunch of dependencies.

Question:

Assuming my Application class is like following:

import android.app.Application;

public class MyApp extends Application {

    public String example(){
        return "Test";
    }

}

I have some instrumented tests for testing UI. Assuming I have the following test:

public class MyMainActivityTest {

    @Rule
    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
            MainActivity.class);

    @Test
    public void firstTest(){
       onView(withId(R.id.textBt)).perform(click());
       // ...
    }
}

I want to mock example() method inside MyMainActivityTest, let's say that it should return Mock Test instead of Test. How to do it?


Answer:

You should create Class which extends your Application class and put it into test folder.

    public class MyTestApp extends MyApp {

    public String example(){
        return "SuperTest";
    }
}

Then use @Config Annotation from Robolectric library over your test class:

@Config(application = MyTestApp)

This should work for all kind of tests including Espresso UI tests, if it isn't you can try to use custom TestRunner with your TestApp class like this:

public class MyRunner extends AndroidJUnitRunner {
  @Override
  public Application newApplication(ClassLoader cl, String className, Context context)
      throws Exception {
    return super.newApplication(cl, MyTestApp.class.getName(), context);
  }
}

And put this over your Test class: @RunWith(MyRunner.class)

Question:

I have an application which displays data (posts) from a web API.

  • A background service syncs this data at some unknown time and saves it.
  • When visiting my main activity it loads this data and displays it in a RecyclerView
  • The loading is handled via a singleton class

I currently test the main activity as follows

@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);

@Test
public void testDataLoad() {
    int postsTotal = DataSingleton.getInstance().getPostsCount();
    ViewInteraction empty = onView(withId(R.id.empty_view));
    ViewInteraction recycler = onView(withId(R.id.recycler_view));
    if (postsTotal == 0) {
        empty.check(matches(isDisplayed()));
        recycler.check(matches(not(isDisplayed())));
    } else {
        empty.check(matches(not(isDisplayed())));
        recycler.check(matches(isDisplayed()));
        recycler.check(new RecyclerViewItemCountAssertion(greaterThan(postsTotal)));
    }
}

I know that this can't be the right way to write tests. I want to be able to test both with an empty data set and a non-empty set so that the if-else is two separate tests. The only way I think I can achieve it is to mock the data.

Is there another way? Can I use Mockito to make the MainActivity use mock data without modifying the production code? Is my only choice to make it inject either real or mocked data providers in place of my singleton?

Is it better to just uninstall and reinstall my app each time so there is no data to start with and then continue with real data testing?


Answer:

Android Activity are heavyweight and hard to test. Because we don't have control over the constructor, it is hard to swap in test doubles.

The first thing to do is to make sure you are depending on an abstraction of the data-source rather than a concretion. So if you are using a singleton with a getPostsCount() method then extract an interface:

interface DataSourceAbstraction {
    int getPostsCount();
}

Make a wrapper class that implements your interface:

class ConcreteDataSource implements DataSourceAbstraction {
    @Override
    int getPostsCount() {
        return DataSingleton.getInstance().getPostsCount();
    }
}

And make the Activity depend on that rather than the concrete DataSingleton

DataSourceAbstraction dataSourceAbstraction;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super(savedInstanceState);
    injectMembers();
}

@VisibleForTesting
void injectMembers() {
    dataSourceAbstraction = new ConcreteDataSource();
}

You can now swap in a test double by subclassing and overriding injectMembers that has relaxed visibility. It's a bad idea do this in enterprise development, but there are less options in Android Activities where you don't control the constructor of the class.

You can now write:

DataSourceAbstraction dataSource;

//system under test
MainActivity mainActivity

@Before
public void setUp() {
    mockDataSource = Mockito.mock(DataSourceAbstraction.class);
    mainActivity = new MainActivity() {
        @Override
        void injectMembers() {
            dataSourceAbstraction = mockDataSource;
        }
    };
}