Android Testing with Robolectric and Dagger

前端 未结 3 1003
一生所求
一生所求 2021-01-24 23:33

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

相关标签:
3条回答
  • 2021-01-24 23:58

    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.

    0 讨论(0)
  • 2021-01-25 00:04

    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.

    0 讨论(0)
  • 2021-01-25 00:23

    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());
        }
    }
    
    0 讨论(0)
提交回复
热议问题