I am trying to write an Android application using Dagger. Trying to follow the TDD approach, I started writing a test for my First activity. For writing tests I am using Robolectric and I am trying to make it work in different scenarios using Mockito.
Short story:
I have an android activity which I want to test using robolectric. This activity has some of its dependencies provided through Dagger. I managed to make this work by overriding the Application class and providing a mock of the utility class. What I need now is to be able to change the behavior of the utility class(using Mockito) in the same unit test file to test different scenarios.
Long story:
Development and testing environment: Android Studio 0.8.6, gradle 0.12, dagger 1.2.2, robolectric 2.3
Base Application class:
public class MyApplication extends DaggerApplication
{
@Override
protected List<Object> getAppModules() {
List<Object> modules = new ArrayList<Object>();
modules.add(new AppModule(this));
return modules;
}
}
DaggerApplication class:
public abstract class DaggerApplication extends Application {
private ObjectGraph mObjectGraph;
@Override
public void onCreate() {
super.onCreate();
AndroidAppModule sharedAppModule = new AndroidAppModule(this);
List<Object> modules = new ArrayList<Object>();
modules.add(sharedAppModule);
modules.addAll(getAppModules());
mObjectGraph = ObjectGraph.create(modules.toArray());
}
protected abstract List<Object> getAppModules();
@Override
public void inject(Object object) {
mObjectGraph.inject(object);
}
@Override
public ObjectGraph getObjectGraph() {
return mObjectGraph;
}
}
Test Application:
public class TestMyApplication extends MyApplication{
@Override
protected List<Object> getAppModules() {
List<Object> modules = super.getAppModules();
modules.add(new GeneralUtilsModuleNoInternetConnection());
return modules;
}
public static <T> void injectMocks(T object) {
CursuriDeSchimbApplication app = (TestCursuriDeSchimbApplication) Robolectric.application;
app.inject(object);
}
}
AppModule class:
@Module(
injects = {
SplashScreenActivity.class
},
includes = AndroidAppModule.class
)
public class AppModule {
private Context app;
public AppModule()
{
}
public AppModule(Context app) {
this.app = app;
}
@Provides
@Singleton
GeneralUtils provideGeneralUtils() {
return new GeneralUtils();
}
}
Test Module class:
@Module(
includes = AppModule.class,
injects = {SplashScreenActivityTest.class,
SplashScreenActivity.class},
overrides = true
)
public class GeneralUtilsModuleNoInternetConnection
{
public GeneralUtilsModuleNoInternetConnection() {
}
@Provides
@Singleton
GeneralUtils provideGeneralUtils() {
GeneralUtils mockGeneralUtils = Mockito.mock(GeneralUtils.class);
when(mockGeneralUtils.isInternetConnection()).thenReturn(false);
return mockGeneralUtils;
}
}
Test class:
@RunWith(RobolectricTestRunner.class)
public class SplashScreenActivityTest
{
SplashScreenActivity activity;
@Before
public void setUp()
{
activity = Robolectric.buildActivity(SplashScreenActivity.class).create().get();
}
@Test
public void testOnCreate_whenNoInternetConnection()
{
<!-- Here I want GeneralUtils to return false when asking for internet connection -->
}
@Test
public void testOnCreate_whenThereIsInternetConnection()
{
<!-- Here I want GeneralUtils to return true when asking for internet connection -->
}
}
If you need more information please do ask. To summarize: I would like to know how to use different test dagger modules in the same test class for different test scenarios.
Thank you.
Ok, first off, user2511882 I have tried your solution before posting the question but the thing is, if you look at the structure of TestMyApplication, where I inject the test module, you would see that your suggestion and my previous tries could not work.
After rethinking the whole problem I have found a solution along the lines of my initial tries and also a more useful solution (as far as I can see it). First off, I do not rely on the TestMyApplication class anymore. Furthermore I had to do some changes to MyApplication class to make it more "test friendly" (without changing its functionality). So MyApplication class looks like this:
Now I can create the two test modules, one in which I set the behavior to return true when asking for an internet connection and one which will return false for the same query.
Now, in my test class I would have the following:
This works fine and is along the lines of my initial plan of testing. But I think there is a more elegant solution instead of creating a new test module for each situation. The modified test module looks like this:
Using this, the Test class looks like this:
Thank you all for your help and I really hope that this solution will help others achieve better testing on Android.
Seems like you're looking for module override (like Roboguice does). I couldn't find any, but in my tests, I've been using something like this:
If
MyObject
is used in anActivity
, I can also test it:Here's what you can do. Create two different
modules
in the test class. One which provides Internet Connection as true and another as Internet Connection as False. Once you have the two different module's setup inject them in the individual test class rather than thesetUp
of the Test Class. So:The second module:
And in you test class:
Since you are injecting the modules in the test class itself, their scope is just local and you should be just fine.
Another way is you might just
inject
one of the modules insetUp
. Use it across all the test cases. And just for the test that you need internet connection, inject theGeneralUtilsModuleWithInternetConnection
in the test itself.Hope this helps.