Hot questions for Using Mockito in 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; } }; }