Is this a correct way to use Dagger 2 for Android app in unit test to override dependencies with mocks/fakes?

后端 未结 2 936
别跟我提以往
别跟我提以往 2021-02-02 12:55

For \'regular\' Java project overriding the dependencies in the unit tests with mock/fake ones is easy. You have to simply build your Dagger component and give

相关标签:
2条回答
  • 2021-02-02 13:43

    1. Inject over dependencies

    Two things to note:

    1. Components can provide themselves
    2. If you can inject it once, you can inject it again (and override the old dependencies)

    What I do is just inject from my test case over the old dependencies. Since your code is clean and everything is scoped correctly nothing should go wrong—right?

    The following will only work if you don't rely on Global State since changing the app component at runtime will not work if you keep references to the old one at some place. As soon as you create your next Activity it will fetch the new app component and your test dependencies will be provided.

    This method depends on correct handling of scopes. Finishing and restarting an activity should recreate its dependencies. You therefore can switch app components when there is no activity running or before starting a new one.

    In your testcase just create your component as you need it

    // in @Test or @Before, just inject 'over' the old state
    App app = (App) InstrumentationRegistry.getTargetContext().getApplicationContext();
    AppComponent component = DaggerAppComponent.builder()
            .appModule(new AppModule(app))
            .build();
    component.inject(app);
    

    If you have an application like the following...

    public class App extends Application {
    
        @Inject
        AppComponent mComponent;
    
        @Override
        public void onCreate() {
            super.onCreate();
            DaggerAppComponent.builder().appModule(new AppModule(this)).build().inject(this);
        }
    }
    

    ...it will inject itself and any other dependencies you have defined in your Application. Any subsequent call will then get the new dependencies.


    2. Use a different configuration & Application

    You can chose the configuration to be used with your instrumentation test:

    android {
    ...
        testBuildType "staging"
    }
    

    Using gradle resource merging this leaves you with the option to use multiple different versions of your App for different build types.

    Move your Application class from the main source folder to the debug and release folders. Gradle will compile the right source set depending on the configuration. You then can modify your debug and release version of your app to your needs.

    If you do not want to have different Application classes for debug and release, you could make another buildType, used just for your instrumentation tests. The same principle applies: Duplicate the Application class to every source set folder, or you will receive compile errors. Since you would then need to have the same class in the debug and rlease directory, you can make another directory to contain your class used for both debug and release. Then add the directory used to your debug and release source sets.

    0 讨论(0)
  • 2021-02-02 13:43

    There is a simpler way to do this, even the Dagger 2 docs mention it but they don't make it very obvious. Here's a snippet from the documentation.

    @Provides static Pump providePump(Thermosiphon pump) {
        return pump;
    }
    

    The Thermosiphon implements Pump and wherever a Pump is requested Dagger injects a Thermosiphon.

    Coming back to your example. You can create a Module with a static boolean data member which allows you to dynamically switch between your real and mock test objects, like so.

    @Module
    public class HttpModule {
    
        private static boolean isMockingHttp;
    
        public HttpModule() {}
    
        public static boolean mockHttp(boolean isMockingHttp) {
            HttpModule.isMockingHttp = isMockingHttp;
        }
    
        @Provides
        HttpClient providesHttpClient(OkHttpClient impl, MockHttpClient mockImpl) {
            return HttpModule.isMockingHttp ? mockImpl : impl;
        }
    
    }
    

    HttpClient can be the super class which is extended or an interface which is implemented by OkHttpClient and MockHttpClient. Dagger will automatically construct the required class and inject it's internal dependencies just like Thermosiphon.

    To mock your HttpClient, just call HttpModule.mockHttp(true) before your dependencies are injected in your application code.

    The benefits to this approach are:

    • No need to create separate test components since the mocks are injected at a module level.
    • Application code remains pristine.
    0 讨论(0)
提交回复
热议问题