Android: Writing test cases for Fragments - android

In my previous projects I've done most of the work through Activities and used ActivityInstrumentationTestCase2 as per the document:
http://developer.android.com/tools/testing/activity_testing.html
I have an idea how to work with Activity Test cases; but when it comes to Fragment ,I don't have much idea nor found much documents related to that.
So how to write test cases when I have several fragments with one or two actvities?
Any example code or sample would be more helpful.

Here's a rough guide using ActivityInstrumentationTestCase2:
Step 1. Create a blank Activity to hold your fragment(s)
private static class FragmentUtilActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout view = new LinearLayout(this);
view.setId(1);
setContentView(view);
}
}
Step 2:
Inside your test, instantiate your fragment and add it to the blank activity
public class MyFragmentTest extends ActivityInstrumentationTestCase2<FragmentUtilActivity> {
private MyFragment fragment;
#Before
public void setup() {
fragment = new MyFragment();
getActivity().getFragmentManager().beginTransaction().add(1, fragment, null).commit();
}
}
Step 3 Test your instantiated fragment
#Test
public void aTest() {
fragment.getView().findViewById(...);
}
If you're using robolectric, this is pretty straightforward using the FragmentUtilTest class:
#Test
public void aTest() {
// instantiate your fragment
MyFragment fragment = new MyFragment();
// Add it to a blank activity
FragmentTestUtil.startVisibleFragment(fragment);
// ... call getView().findViewById() on your fragment
}

Here is my working solution:
Create an instrumentation unit test class for this in androidTest directory, i.e.:
public class FragmentTest extends
ActivityInstrumentationTestCase2<MainActivity> {
private MainActivity testingActivity;
private TestFragment testFragment;
//...
}
call this constructor inside this new class:
public FragmentTest() {
super(MainActivity.class);
}
override the setUp() method (be sure to have R.id.fragmentContainer in your Activity class) where you will call at the end waitForIdleSync():
#Override
protected void setUp() throws Exception {
super.setUp();
// Starts the activity under test using
// the default Intent with:
// action = {#link Intent#ACTION_MAIN}
// flags = {#link Intent#FLAG_ACTIVITY_NEW_TASK}
// All other fields are null or empty.
testingActivity = getActivity();
testFragment = new TestFragment();
testingActivity.getFragmentManager().beginTransaction().add(R.id.fragmentContainer,testFragment,null).commit();
/**
* Synchronously wait for the application to be idle. Can not be called
* from the main application thread -- use {#link #start} to execute
* instrumentation in its own thread.
*
* Without waitForIdleSync(); our test would have nulls in fragment references.
*/
getInstrumentation().waitForIdleSync();
}
Write a test method, for example somethng like:
public void testGameFragmentsTextViews() {
String empty = "";
TextView textView = (TextView)testFragment.getView().findViewById(R.id.myTextView);
assertTrue("Empty stuff",(textView.getText().equals(empty)));
}
Run the test.

AndroidX provides a library, FragmentScenario, to create fragments and change their state.
app/build.gradle
dependencies {
def fragment_version = "1.0.0"
// ...
debugImplementation 'androidx.fragment:fragment-testing:$fragment_version'
}
example
#RunWith(AndroidJUnit4::class)
class MyTestSuite {
#Test fun testEventFragment() {
// The "fragmentArgs" and "factory" arguments are optional.
val fragmentArgs = Bundle().apply {
putInt("selectedListItem", 0)
}
val factory = MyFragmentFactory()
val scenario = launchFragmentInContainer<MyFragment>(
fragmentArgs, factory)
onView(withId(R.id.text)).check(matches(withText("Hello World!")))
}
}
More at official docs.

Use your main activity as the test activity that you send to ActivityInstrumentationTestCase2. Then you can work with the fragments through the fragment manager of your main activity that launches the fragments. This is even better than having a test activity because it uses the logic that you write in your main activity to test scenarios, which gives a fuller and more complete test.
Example:
public class YourFragmentTest extends ActivityInstrumentationTestCase2<MainActivity> {
public YourFragmentTest(){
super(MainActivity.class);
}
}

Right now ActivityInstrumentationTestCase2 is deprecated. Now you can use rules in order to use activities within your tests: http://wiebe-elsinga.com/blog/whats-new-in-android-testing/
In order for those to work you'll have to add the dependencies to you build.gradle:
testCompile 'com.android.support.test:rules:0.5'
testCompile 'com.android.support.test:runner:0.5'
(See Why cannot I import AndroidJUnit4 and ActivityTestRule into my unit test class?)

Related

Mock activity class member before onCreate

I am trying to test an activity using robolectric 3.3.2.
Want to mock and activity's member initialization as the direct call results in NPE.
ActivityController<MyActivity> activityController =
Robolectric.buildActivity(MyActivity.class);
mTestActivity = activityController.get();
Mockito.when(mTestActivity.getCountry()).thenReturn("xxxx");
activityController.setup();
Tried out above code, but the setup.() (oncreate) ignores the mock of
getCountry method and invokes the definition from activity.
Is there a way to achieve this?
It will not work like this even if you use spies (#Spy, Mockito.spy()).
You should use stub:
public class MyActivityTest{
public static class StubMyActivity extends MyActivity {
Country getCountry() {
return someSpecialCountry;
}
}
#Before
public void setUp(){
ActivityController<StubMyActivity> activityController =
Robolectric.buildActivity(StubMyActivity.class);
mTestActivity = activityController.setup().get();
}
}

How to get the activity reference before its oncreate gets called during testing

How to get the reference of Activity before its onCreate will be called. while its under test. I use ActivityTestRule as JUnit Rule. The reason for this requirement is i want to inject Mocks into activity from tests.
public class MyActivity extends Activity{
MyComponent myComponent;
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
if(myComponent==null){
myComponent ... //initialise dagger component
}
myComponent.inject(this);
...
}
public void setComponent(MyComponent comp){
this.myComponent = comp;
}
}
public class MyTest{
#Rule
public ActivityTestRule<MyActivity> intentsTestRule = new ActivityTestRule<>(MyActivity.class);
MyComponent myFakeComponent;
#Before
public void setUp() {
MyActivity activity = intentsTestRule.getActivity();
activity.setComponent(myFakeComponent);
}
#Test
public void testMethod1(){...}
}
As per documentation, what you're doing here is wrong.
#Rule
public ActivityTestRule<MyActivity> intentsTestRule = new ActivityTestRule<>(MyActivity.class);
MyComponent myFakeComponent;
#Before
public void setUp() {
MyActivity activity = intentsTestRule.getActivity();
activity.setComponent(myFakeComponent);
}
Because,
This rule provides functional testing of a single activity.
The activity under test will be launched before each test annotated with
Test and before methods annotated with #Before.
It will be terminated after the test is completed and methods
annotated with After are finished. During the duration of the test
you will be able to manipulate your Activity directly.
However!
protected void beforeActivityLaunched ()
Override this method to execute any code that should run
before your Activity is created and launched.
This method is called before each test method,
including any method annotated with #Before.
Therefore, if you move the initialization of the MainActivityComponent outside the Activity to a place that is mockable, then you'll be able to tinker it together before the main activity is created.
EDIT:
Another possible solution is to lazily initiate the Activity as per link.
#Rule
public ActivityTestRule<NoteDetailActivity> mNoteDetailActivityTestRule =
new ActivityTestRule<>(NoteDetailActivity.class, true /* Initial touch mode */,
false /* Lazily launch activity */);
#Before
public void intentWithStubbedNoteId() {
// Add a note stub to the fake service api layer.
FakeNotesServiceApiImpl.addNotes(NOTE);
// Lazily start the Activity from the ActivityTestRule this time to inject the start Intent
Intent startIntent = new Intent();
startIntent.putExtra(NoteDetailActivity.EXTRA_NOTE_ID, NOTE.getId());
mNoteDetailActivityTestRule.launchActivity(startIntent);
registerIdlingResource();
}
Here is my sample code for that:
public class TestClass {
#Rule
public ActivityTestRule<T> activityRule = new ActivityTestRule<T>(type) {
#Override
protected void beforeActivityLaunched() {
//TODO inject mocks, setup stubs etc..
}
};
}
#Before
public void before() {
activityRule.getActivity();
}
#Test
public void myTest() {
//...
}
}
Is this code complete? I can't see you creating the dagger graph.
Anyway, what I do in my code, is to have a Static class called Injector that creates the graph for me, and also can inject elements into objects. So, in my Application Class I call it to create the graph, and all other activities just use the existent graph.
Then, in a test, you could create a fake test application class that initialize the graph in a different way, or simply recreate the graph calling the Injector methods, before the activity is created. I'm not familiar with ActivityTestRule, so I can't help much with the life cycle of this test.
But just make sure you create a new graph before the activity is created, and let the activity just use the existent graph.
How the activity access the graph? Well, I don't really love it, but we are used to access the application class (with explicit cast) and ask it to inject the dependencies for us. This is the way Dagger examples do it also.

Is there a way to run Espresso test with multiple test methods but only one setup method?

I have a simple test today:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class WhenNavigatingToUsersView {
#Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule(MainActivity.class);
private MainActivity mainActivity;
#Before
public void setActivity() {
mainActivity = mActivityRule.getActivity();
onView(allOf(withId(R.id.icon), hasSibling(withText(R.string.users)))).perform(click());
}
#Test
public void thenCorrectViewTitleShouldBeShown() {
onView(withText("This is the Users Activity.")).check(matches(isDisplayed()));
}
#Test
public void thenCorrectUserShouldBeShown() {
onView(withText("Donald Duck (1331)")).check(matches(isDisplayed()));
}
}
But for every test method the setActivity is run, which, if you have 10-15 methods, in the end will be time consuming (if you have a lot of views too).
#BeforeClass doesn't seem to work since it has to be static and thus forcing the ActivityTestRule to be static as well.
So is there any other way to do this? Rather than having multiple asserts in the same test method?
#Before annotation should only precede methods containing preliminary setup. Initialization of needed objects, getting the current session or the current activity, you get the idea.
It is replacing the old setUp() method from the ActivityInstrumentationTestCase2, just as #After replaces the tearDown().
That means that it is intended to be executed before every test in the class and it should stay that way.
You should have no ViewInteraction, no DataInteraction, no Assertions nor View actions in this method, since that is not its purpose.
In your case, simply remove the onView() call from setActivity() and put it inside the actual test methods, in every test method if necessary, like so:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class WhenNavigatingToUsersView {
#Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule(MainActivity.class);
private MainActivity mainActivity;
#Before
public void setActivity() {
mainActivity = mActivityRule.getActivity();
// other required initializations / definitions
}
#Test
public void thenCorrectViewTitleShouldBeShown() {
onView(allOf(withId(R.id.icon), hasSibling(withText(R.string.users)))).perform(click());
onView(withText("This is the Users Activity.")).check(matches(isDisplayed()));
}
#Test
public void thenCorrectUserShouldBeShown() {
onView(allOf(withId(R.id.icon), hasSibling(withText(R.string.users)))).perform(click());
onView(withText("Donald Duck (1331)")).check(matches(isDisplayed()));
}
}
Another option for you would be separating these tests.
Clicking on the user's icon will be in the HomeActivity test class while the rest of the tests will be in the UserActivity test class.
UserActivity test class will launch UserActivity with the proper Intent ( you can do so by passing the false Boolean into the Rule constructor and calling launchActivity(intent) manually).
This will eliminate the necessity of setting up the activity every single time. It will also get rid of constant dependency on the main activity. If something goes wrong, your UserActivity tests will be intact and will produce the results, while the issue will be caught by the test in the MainActivity.
Actually, by doing so your tests will might become MediumSize as the runtime will drastically decrease.
You can try this :
**** Setting ****
public void testStory() throws Exception {
}
public void testStory2() throws Exception {
}
public void testStory3() throws Exception {
}
Try to run your test by this command:
./gradlew cC
Did you try doing it as follows or a minor variation of it to suit your needs:
#Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class);
private MainActivity mainActivity = mActivityRule.getActivity();
#BeforeClass
public static void setActivity() {
onView(allOf(withId(R.id.icon), hasSibling(withText(R.string.users)))).perform(click());
}
This way, you 'mainActivity' need not be static. Also, the setActivity() method will get called only once.

Configuration change using Robolectric

To retain my AsyncTasks across configuration changes, I use a fragment-based solution with setRetainInstance(true), which hosts each AsyncTask and calls back to a listening Activity, similar to this solution http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
Ultimately, the purpose is to test the AsyncTask's retention functionality throughout configuration changes using Robolectric, but I need to start with setting up the actual configuration change correctly. However, it seems I can't mimic the exact reference behavior that occurs during a configuration change.
Real app: When running a real app, on configuration change, the Activity is destroyed and recreated while the Fragment is retained, so it seems to be working. I can see this by checking their references before and after the configuration change (example references used below):
Real app, before:
Activity: abc
Fragment: xyz
Real app, after:
Activity: bca
Fragment: xyz (properly retained and reattached)
Case 1: When running recreate() on the Activity in the Robolectric test, however, the Activity doesn't seem to have its instance properly recreated (despite the docs saying the method performs all the lifecycle calls):
mActivityController =
Robolectric.buildActivity(AsyncTaskTestActivity.class).attach().create().start().resume().visible();
mActivity = mActivityController.get();
mActivity.recreate();
Robolectric with recreate(), before:
Activity: abc
Fragment: xyz
Robolectric with recreate(), after
Activity: abc
Fragment: xyz
This leads me to believe that a new Activity instance isn't properly created and the reattachment functionality therefore hasn't happened in a real way.
Case 2: If I create the test based on individual lifecycle calls instead:
mActivityController = Robolectric.buildActivity(AsyncTaskTestActivity.class).attach().create().start().resume().visible();
mActivityController.pause().stop().destroy();
mActivityController = Robolectric.buildActivity(AsyncTaskTestActivity.class).attach().create().start().resume().visible();
In this version, it seems the Activity gets fully replaced from scratch, but so does also the Fragment:
Robolectric with separate lifecycle calls, before
Activity: abc
Fragment: xyz
Robolectric with separate lifecycle calls, after
Activity: bca
Fragment: yzx
It seems I'm either reusing the same Activity (case 1) or replacing everything with new instances, as if there is no underlying Application that retains the Fragment (case 2).
Question: is there any way I can set up my Robolectric test to mimic the reference result that I get when running the app in an actual Android environment (as per the Real app result), or am I stuck with either creating a separate test app or settling with Robotium functional tests? I tried to do it like this https://stackoverflow.com/a/26468296 but got the same result as my case 2.
Thanks in advance!
I have played around a bit and came up with a solution using Robolectric 3.0 and Mockito:
#RunWith(RobolectricGradleTestRunner.class)
#Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.KITKAT, shadows = {ExampleActivityTest.ExampleActivityShadow.class})
public class ExampleActivityTest {
#Mock
private FragmentManager fragmentManagerMock;
#Before
public void setup() {
initMocks(this);
setupFragmentManagerMock();
}
#Test
public void testRestoreAfterConfigurationChange() {
// prepare
ActivityController<ExampleActivity> controller = Robolectric.buildActivity(ExampleActivity.class);
ExampleActivity activity = controller.get();
ExampleActivityShadow shadow = (ExampleActivityShadow) Shadows.shadowOf(activity);
shadow.setFragmentManager(fragmentManagerMock);
ActivityController<ExampleActivity> controller2 = Robolectric.buildActivity(ExampleActivity.class);
ExampleActivity recreatedActivity = controller2.get();
ExampleActivityShadow recreatedActivityShadow = (ExampleActivityShadow) Shadows.shadowOf(recreatedActivity);
recreatedActivityShadow.setFragmentManager(fragmentManagerMock);
// run & verify
controller.create().start().resume().visible();
activity.findViewById(R.id.inc_button).performClick();
activity.findViewById(R.id.inc_button).performClick();
assertEquals(2, activity.lostCount.count);
assertEquals(2, activity.retainedCount.count);
Bundle bundle = new Bundle();
controller.saveInstanceState(bundle).pause().stop().destroy();
controller2.create(bundle).start().restoreInstanceState(bundle).resume().visible();
assertEquals(0, recreatedActivity.lostCount.count);
assertEquals(2, recreatedActivity.retainedCount.count);
}
private void setupFragmentManagerMock() {
final HashMap<String, Fragment> fragments = new HashMap<>();
doAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return fragments.get(invocation.getArguments()[0]);
}
}).when(fragmentManagerMock).findFragmentByTag(anyString());
final HashMap<String, Fragment> fragmentsToBeAdded = new HashMap<>();
final FragmentTransaction fragmentTransactionMock = mock(FragmentTransaction.class);
doAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
fragmentsToBeAdded.put((String) invocation.getArguments()[1], (Fragment) invocation.getArguments()[0]);
return fragmentTransactionMock;
}
}).when(fragmentTransactionMock).add(any(Fragment.class), anyString());
doAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
fragments.putAll(fragmentsToBeAdded);
return null;
}
}).when(fragmentTransactionMock).commit();
when(fragmentManagerMock.beginTransaction()).thenReturn(fragmentTransactionMock);
}
#Implements(Activity.class)
public static class ExampleActivityShadow extends ShadowActivity {
private FragmentManager fragmentManager;
#Implementation
public FragmentManager getFragmentManager() {
return fragmentManager;
}
public void setFragmentManager(FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
}
}
}
Note that I have only mocked the methods of FragmentManager (beginTransaction() and findFragmentByTag()) and FragmentTransaction (add() and commit()) that I use in my code, so you might need to expand these depending on your code.
I haven't done too much work with Robolectric yet, so there may be a more elegant solution to this, but this works for me for now.
You can see the full source code and project setup here: https://github.com/rgeldmacher/leash (might be worth a look if you still need to retain objects ;) )

Getting Dagger to inject mock objects when doing Espresso functional testing for Android

I've recently gone whole-hog with Dagger because the concept of DI makes complete sense. One of the nicer "by-products" of DI (as Jake Wharton put in one of his presentations) is easier testability.
So now I'm basically using Espresso to do some functional testing, and I want to be able to inject dummy/mock data to the application and have the activity show them up. I'm guessing since, this is one of the biggest advantages of DI, this should be a relatively simple ask. For some reason though, I can't seem to wrap my head around it. Any help would be much appreciated. Here's what I have so far (I've written up an example that reflects my current setup):
public class MyActivity
extends MyBaseActivity {
#Inject Navigator _navigator;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApplication.get(this).inject(this);
// ...
setupViews();
}
private void setupViews() {
myTextView.setText(getMyLabel());
}
public String getMyLabel() {
return _navigator.getSpecialText(); // "Special Text"
}
}
These are my dagger modules:
// Navigation Module
#Module(library = true)
public class NavigationModule {
private Navigator _nav;
#Provides
#Singleton
Navigator provideANavigator() {
if (_nav == null) {
_nav = new Navigator();
}
return _nav;
}
}
// App level module
#Module(
includes = { SessionModule.class, NavigationModule.class },
injects = { MyApplication.class,
MyActivity.class,
// ...
})
public class App {
private final Context _appContext;
AppModule(Context appContext) {
_appContext = appContext;
}
// ...
}
In my Espresso Test, I'm trying to insert a mock module like so:
public class MyActivityTest
extends ActivityInstrumentationTestCase2<MyActivity> {
public MyActivityTest() {
super(MyActivity.class);
}
#Override
public void setUp() throws Exception {
super.setUp();
ObjectGraph og = ((MyApplication) getActivity().getApplication()).getObjectGraph().plus(new TestNavigationModule());
og.inject(getActivity());
}
public void test_SeeSpecialText() {
onView(withId(R.id.my_text_view)).check(matches(withText(
"Special Dummy Text")));
}
#Module(includes = NavigationModule.class,
injects = { MyActivityTest.class, MyActivity.class },
overrides = true,
library = true)
static class TestNavigationModule {
#Provides
#Singleton
Navigator provideANavigator() {
return new DummyNavigator(); // that returns "Special Dummy Text"
}
}
}
This is not working at all. My Espresso tests run, but the TestNavigationModule is completely ignored... arr... :(
What am I doing wrong? Is there a better approach to mocking modules out with Espresso? I've searched and seen examples of Robolectric, Mockito etc. being used. But I just want pure Espresso tests and need to swap out a module with my mock one. How should i be doing this?
EDIT:
So I went with #user3399328 approach of having a static test module list definition, checking for null and then adding it in my Application class. I'm still not getting my Test injected version of the class though. I have a feeling though, its probably something wrong with dagger test module definition, and not my espresso lifecycle. The reason I'm making the assumption is that I add debug statements and find that the static test module is non-empty at time of injection in the application class. Could you point me to a direction of what I could possibly be doing wrong. Here are code snippets of my definitions:
MyApplication:
#Override
public void onCreate() {
// ...
mObjectGraph = ObjectGraph.create(Modules.list(this));
// ...
}
Modules:
public class Modules {
public static List<Object> _testModules = null;
public static Object[] list(MyApplication app) {
// return new Object[]{ new AppModule(app) };
List<Object> modules = new ArrayList<Object>();
modules.add(new AppModule(app));
if (_testModules == null) {
Log.d("No test modules");
} else {
Log.d("Test modules found");
}
if (_testModules != null) {
modules.addAll(_testModules);
}
return modules.toArray();
}
}
Modified test module within my test class:
#Module(overrides = true, library = true)
public static class TestNavigationModule {
#Provides
#Singleton
Navigator provideANavigator()() {
Navigator navigator = new Navigator();
navigator.setSpecialText("Dummy Text");
return navigator;
}
}
With Dagger 2 and Espresso 2 things have indeed improved. This is how a test case could look like now. Notice that ContributorsModel is provided by Dagger. The full demo available here: https://github.com/pmellaaho/RxApp
#RunWith(AndroidJUnit4.class)
public class MainActivityTest {
ContributorsModel mModel;
#Singleton
#Component(modules = MockNetworkModule.class)
public interface MockNetworkComponent extends RxApp.NetworkComponent {
}
#Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
MainActivity.class,
true, // initialTouchMode
false); // launchActivity.
#Before
public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
RxApp app = (RxApp) instrumentation.getTargetContext()
.getApplicationContext();
MockNetworkComponent testComponent = DaggerMainActivityTest_MockNetworkComponent.builder()
.mockNetworkModule(new MockNetworkModule())
.build();
app.setComponent(testComponent);
mModel = testComponent.contributorsModel();
}
#Test
public void listWithTwoContributors() {
// GIVEN
List<Contributor> tmpList = new ArrayList<>();
tmpList.add(new Contributor("Jesse", 600));
tmpList.add(new Contributor("Jake", 200));
Observable<List<Contributor>> testObservable = Observable.just(tmpList);
Mockito.when(mModel.getContributors(anyString(), anyString()))
.thenReturn(testObservable);
// WHEN
mActivityRule.launchActivity(new Intent());
onView(withId(R.id.startBtn)).perform(click());
// THEN
onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 0))
.check(matches(hasDescendant(withText("Jesse"))));
onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 0))
.check(matches(hasDescendant(withText("600"))));
onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 1))
.check(matches(hasDescendant(withText("Jake"))));
onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 1))
.check(matches(hasDescendant(withText("200"))));
}
Your approach doesn't work because it only happens once, and as Matt mentioned, when the activity's real injection code runs, it will wipe out any variables injected by your special object graph.
There are two ways to get this to work.
The quick way: make a public static variable in your activity so a test can assign an override module and have the actual activity code always include this module if it's not null (which will only happen in tests). It's similar to my answer here just for your activity base class instead of application.
The longer, probably better way: refactor your code so that all activity injection (and more importantly graph creation) happens in one class, something like ActivityInjectHelper. In your test package, create another class named ActivityInjectHelper with the exact same package path that implements the same methods, except also plusses your test modules. Because test classes are loaded first, your application will execute with the testing ActivityInjectHelper. Again it's similar to my answer here just for a different class.
UPDATE:
I see you've posted more code and it's close to working, but no cigar. For both activities and applications, the test module needs to be snuck in before onCreate() runs. When dealing with activity object graphs, anytime before the test's getActivity() is fine. When dealing with applications, it's a bit harder because onCreate() has already been called by the time setUp() runs. Luckily, doing it in the test's constructor works - the application hasn't been created at that point. I briefly mention this in my first link.
The call to getActivity will actually start your activity calling onCreate in the process which means you won't be getting your test modules added to the graph in time to be used. Using activityInstrumentationTestcase2 you can't really inject properly at the activity scope. I've worked around this by using my application to provide dependencies to my activities and then inject mock objects into it which the activities will use. It's not ideal but it works. You can use an event bus like Otto to help provide dependencies.
EDIT: the below in post form http://systemdotrun.blogspot.co.uk/2014/11/android-testing-with-dagger-retrofit.html
To test an Activity using Espresso + Dagger I have done the below
Inspired by the answer from #user3399328 I have a DaggerHelper class inside my Application class, which allows the test case to override the #Providers using Test #Modules which supply mocks. As long as
1) This is done before the testCases getActivity() call is made (as my inject call happens in my activity inside Activity.onCreate)
2) tearDown removes the test modules from the object graph.
Examples below.
Note: this is not ideal as this is subject to similar pitfalls of using factory methods for IoC but at least this way its only ever a single call in tearDown() to bring the system under test back to normal.
The DaggerHelper inside my Application class
public static class DaggerHelper
{
private static ObjectGraph sObjectGraph;
private static final List<Object> productionModules;
static
{
productionModules = new ArrayList<Object>();
productionModules.add(new DefaultModule());
}
/**
* Init the dagger object graph with production modules
*/
public static void initProductionModules()
{
initWithModules(productionModules);
}
/**
* If passing in test modules make sure to override = true in the #Module annotation
*/
public static void initWithTestModules(Object... testModules)
{
initWithModules(getModulesAsList(testModules));
}
private static void initWithModules(List<Object> modules)
{
sObjectGraph = ObjectGraph.create(modules.toArray());
}
private static List<Object> getModulesAsList(Object... extraModules)
{
List<Object> allModules = new ArrayList<Object>();
allModules.addAll(productionModules);
allModules.addAll(Arrays.asList(extraModules));
return allModules;
}
/**
* Dagger convenience method - will inject the fields of the passed in object
*/
public static void inject(Object object) {
sObjectGraph.inject(object);
}
}
My Test module inside my test class
#Module (
overrides = true,
injects = ActivityUnderTest.class
)
static class TestDataPersisterModule {
#Provides
#Singleton
DataPersister provideMockDataPersister() {
return new DataPersister(){
#Override
public void persistDose()
{
throw new RuntimeException("Mock DI!"); //just a test to see if being called
}
};
}
}
Test method
public void testSomething()
{
MyApp.DaggerHelper.initWithTestModules(new TestDataPersisterModule());
getActivity();
...
}
Tear down
#Override
public void tearDown() throws Exception
{
super.tearDown();
//reset
MyApp.DaggerHelper.initProductionModules();
}

Categories

Resources