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 Robole
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 the setUp
of the Test Class. So:
@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;
}
}
The second module:
@Module(
includes = AppModule.class,
injects = {SplashScreenActivityTest.class,
SplashScreenActivity.class},
overrides = true
)
public class GeneralUtilsModuleWithInternetConnection
{
public GeneralUtilsModuleNoInternetConnection() {
}
@Provides
@Singleton
GeneralUtils provideGeneralUtils() {
GeneralUtils mockGeneralUtils = Mockito.mock(GeneralUtils.class);
when(mockGeneralUtils.isInternetConnection()).thenReturn(true);
return mockGeneralUtils;
}
}
And in you test class:
@Test
public void testOnCreate_whenNoInternetConnection()
{
<!-- Here You want to inject the GeneralUtilsModuleNoInternetConnection module and test it out-->
}
@Test
public void testOnCreate_whenThereIsInternetConnection()
{
<!-- Here You want to inject the GeneralUtilsModuleWithInternetConnection module and test it out -->
}
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 in setUp
. Use it across all the test cases. And just for the test that you need internet connection, inject the GeneralUtilsModuleWithInternetConnection
in the test itself.
Hope this helps.
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:
public class MyApplication extends DaggerApplication
{
private List<Object> modules;
public MyApplication() {
modules = new ArrayList<Object>();
modules.add(new AppModule(this));
}
@Override
protected List<Object> getAppModules() {
return modules;
}
}
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:
@RunWith(RobolectricTestRunner.class)
public class SplashScreenActivityTest
{
SplashScreenActivity activity;
public void setUpNoInternet()
{
// Now I can add the new test module to the application modules to override the real one in the application onCreate() method
((MyApplication)Robolectric.application).getAppModules().add(new GeneralUtilsModuleNoInternetConnection());
activity = Robolectric.buildActivity(SplashScreenActivity.class).create().get();
}
public void setUpWithInternet()
{
((MyApplication)Robolectric.application).getAppModules().add(new GeneralUtilsModuleWithInternetConnection());
activity = Robolectric.buildActivity(SplashScreenActivity.class).create().get();
}
@Test
public void testOnCreate_whenNoInternetConnection()
{
setUpNoInternet();
<!-- Assertions -->
}
@Test
public void testOnCreate_whenThereIsInternetConnection()
{
setUpWithInternet();
<!-- Assertions -->
}
}
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:
@Module(
includes = AppModule.class,
injects = {SplashScreenActivityTest.class,
SplashScreenActivity.class},
overrides = true
)
public class GeneralUtilsModuleTest
{
private GeneralUtils mockGeneralUtils;
public GeneralUtilsModuleTest() {
mockGeneralUtils = Mockito.mock(GeneralUtils.class);
}
@Provides
@Singleton
GeneralUtils provideGeneralUtils() {
return mockGeneralUtils;
}
public GeneralUtils getGeneralUtils()
{
return mockGeneralUtils;
}
public void setGeneralUtils(final GeneralUtils generalUtils)
{
this.mockGeneralUtils = generalUtils;
}
}
Using this, the Test class looks like this:
@RunWith(RobolectricTestRunner.class)
public class SplashScreenActivityTest
{
SplashScreenActivity activity;
private GeneralUtilsModuleTest testModule;
private GeneralUtils generalUtils;
@Before
public void setUp()
{
testModule = new GeneralUtilsModuleTest();
generalUtils = Mockito.mock(GeneralUtils.class);
}
public void setUpNoInternet()
{
when(generalUtils.isInternetConnection()).thenReturn(false);
testModule.setGeneralUtils(generalUtils);
((MyApplication)Robolectric.application).getAppModules().add(testModule);
activity = Robolectric.buildActivity(SplashScreenActivity.class).create().get();
}
public void setUpWithInternet()
{
when(generalUtils.isInternetConnection()).thenReturn(true);
testModule.setGeneralUtils(generalUtils);
(MyApplication)Robolectric.application).getAppModules().add(testModule);
activity = Robolectric.buildActivity(SplashScreenActivity.class).create().get();
}
.....(Tests)....
}
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:
MyObjectTest.java
@Test
public void testMyObject() {
ObjectGraph objectGraph = ObjectGraph.create(new TestModule());
MyObject object = objectGraph.get(MyObject.class);
assertNotNull(object);
assertEquals("Received message from MyObjectTestImpl", object.getMessage());
}
TestModule.java
public class TestModule {
@Provides
public Library provideMyObject() {
return new MyObjectTestImpl();
}
}
If MyObject
is used in an Activity
, I can also test it:
@RunWith(RoboGradleTestRunner.class)
public class RoboTest {
@Test
public void testTextView() {
MainActivity activity = (MainActivity) Robolectric.buildActivity(MainActivity.class).create().get();
assertEquals("Received message from MyObjectTestImpl", activity.getMyObject().getMessage());
}
}