Testing Android with Robolectric and Dagger

I am trying to write an Android app using Dagger. Trying to follow the TDD method, I started writing a test for my first activity. I use Robolectric to write tests, and I try to get it to work in different scenarios using Mockito.

Story:

I have an android activity that I want to test with robolectric. This activity has some of its dependencies provided through the Dagger. I managed to do this work by overriding the Application class and providing a utility class layout. Now I need to change the behavior of the utility class (using Mockito) in the same unit test file to test different scripts.

Long story:

Development and testing of the environment: Android Studio 0.8.6, gradle 0.12, dagger 1.2.2, robolectric 2.3

Application base 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; } } 

Application for testing:

 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; } } 

Testing 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 ask. To summarize: I would like to know how to use different test daggers in one test class for different test scenarios.

Thanks.

0
source share
3 answers

Well, first, user2511882 I tried your solution before posting the question, but the fact is that if you look at the TestMyApplication structure, where I insert the test module, you will see that your proposal and my previous attempts cannot work.

After rethinking the whole problem, I found a solution in accordance with my initial attempts, as well as a more useful solution (as far as I can see). First, I no longer rely on the TestMyApplication class. In addition, I had to make some changes to the MyApplication class to make it more "test-friendly" (without changing its functionality). Therefore, the MyApplication class is as follows:

 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 two test modules in which I set the behavior to return true when requesting an Internet connection, and one that returns false for the same request.

Now in my test class I will 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 great and fits my initial test plan. But I think there is a more elegant solution instead of creating a new test module for each situation. The modified test module is as follows:

 @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).... } 

Thanks to everyone for your help, and I really hope that this solution will help others achieve better testing on Android.

+1
source

Here is what you can do. Create two different modules in the test class. One of them provides an Internet connection as true, and the other as an Internet connection as false. After you have set up two different module settings, enter them in a separate test class, and not in the setUp 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; } } 

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 your 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 inject modules into the test class itself, their scope is just local, and you should be fine.

Another way is to simply inject one of the modules in setUp . Use it in all test cases. And just for the test, you need an Internet connection, enter GeneralUtilsModuleWithInternetConnection in the test itself.

Hope this helps.

+2
source

It looks like you are looking for a module override (e.g. Roboguice). I could not find any, but in my tests I used 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 used in an Activity , I can also check it out:

 @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()); } } 
0
source

Source: https://habr.com/ru/post/1209108/


All Articles