Тестовые модули и инъекции в Dagger 2

В настоящее время я разрабатываю приложение Android MVP и пытаюсь разделить свои зависимости в разных модулях Dagger2.

Проблема, с которой я сталкиваюсь, связана с изменением модуля во время модульного тестирования. Сценарий следующий:

  • LoginComponent, который использует два модуля: LoginModule и HTTPModule.
  • LoginModule в одном из своих методов требует экземпляра OkHttp, который предоставляется HTTPModule.

Код следующий:

@Singleton
@Component(modules = {LoginModule.class, HTTPModule.class})
public interface LoginComponent {

}

@Module(includes = {HTTPModule.class})
public class LoginModule {

    @Provides
    @Singleton
    public MyThing provideMyThing(OkHttpClient client) {
       // Do things with it
    }
}

@Module
public class HTTPModule {

    @Provides
    @Singleton
    public OkHttpClient provideOkHttpClient(){
        // Return the OkHttpClient
    }
}

Дело в том, что во время тестирования мне нужно будет изменить возвращаемый OkHttpClient (заставив его принимать все сертификаты, так как когда я запускаю его на JVM, он не принимает сертификат LetsEncrypt).

Также мне это понадобится, потому что мне нужно объявить, что MyTest.class можно внедрить с модулем, а поскольку MyTest.class находится в папке app/src/test/, он не виден для классов, помещенных в app/src/main/. Что я сделал до сих пор, так это скопировал и вставил компонент и модули в папку /test/ и сделал там объявление внедренного класса. Но я знаю, что должен быть правильный способ достичь того, что я ищу.

Еще одна вещь, которую я пробовал, - это аннотировать методы с помощью пользовательских областей видимости (создавая аннотацию @TestScope). Однако это приводит меня к той же проблеме, о которой я говорил ранее: я не могу сделать MyTest.class видимым для компонента, потому что он находится в папке /test/.

Я уже проверил другие подобные вопросы, такие как этот и это еще один, но последний предназначен для запуска тестов с Robolectric, и к настоящему времени я могу выполнять модульное тестирование большей части своего кода только с JUnit4 (Android Studio 2-Beta 8).

Если бы кто-нибудь мог указать мне правильное направление, я был бы более чем благодарен.

Заранее спасибо!


person dexafree    schedule 30.01.2016    source источник
comment
немного раньше вышла статья о тестировании с помощью кинжала, возможно, она могла бы дать вам некоторые ответы [ссылка] medium.com/@fabioCollini/   -  person gropapa    schedule 08.02.2016
comment
Разве вы не можете переопределить HTTPModule при создании компонента в методе Dagger__Component.builder()?   -  person EpicPandaForce    schedule 11.02.2016
comment
@gropapa Я попробую эту статью, спасибо! :)   -  person dexafree    schedule 12.02.2016
comment
@EpicPandaForce HTTPModule — это модуль, и, как я уже сказал, в Dagger 2 похоже, что переопределение метода для модулей не поддерживается, это основная проблема :(   -  person dexafree    schedule 12.02.2016
comment
Вы можете переопределить методы поставщика модуля, если вы не укажете @Provides в переопределенном методе, где вы его переопределили. Вроде new HTTPModule() { @Override public provideOkHttpClient() { return new MockHttpClient(); }; должно работать.   -  person EpicPandaForce    schedule 12.02.2016
comment
@EpicPandaForce вау, я этого не понял. Попробую и отпишусь о результате, спасибо большое!   -  person dexafree    schedule 13.02.2016


Ответы (1)


Вы используете внедрение зависимостей таким образом, что ваш код остается тесно связанным. Вообще говоря, вы хотите, чтобы ваши зависимости были интерфейсами, а не реальными классами. Это делает ваш код красивым и свободным, легко читаемым, модифицируемым и поддерживаемым.

Скройте свои сетевые операции за интерфейсом, чтобы вы могли изменять реализацию сети, когда вам это нужно. Это можно сделать для тестирования - в вашем случае, но это также позволит вам отключить сетевую библиотеку, если вы захотите или вам понадобится в будущем, без изменения какого-либо другого кода.

Попробуйте что-то вроде этого:

@Module
public class HTTPModule {

    @Provides
    @Singleton
    public NetworkProvider provideNetworkProvider(){
        // Return the Network provider
    }
}

Уровень сетевой абстракции:

public interface NetworkProvider {
    // Methods to send requests and receive async responses
}

Реализация OkHttp:

public class OkHttpNetworkProvider implements NetworkProvider {
   // Implement NetworkProvider. This is the only class that
   // knows about OkHttp and its components
}

Теперь вы можете создать фиктивную версию NetworkProvider и использовать ее для тестирования либо через тестовый модуль, либо напрямую.

person Itai Hanski    schedule 11.02.2016
comment
Спасибо за ответ, но это не совсем то, о чем я просил. Я хочу вернуть OkHttpClient в обоих случаях, поэтому интерфейс здесь не нужен (если только вы не хотите добавить еще один слой). Проблема, с которой я сталкиваюсь, заключается в переопределении возвращенной реализации во время тестирования. - person dexafree; 12.02.2016
comment
Еще один слой неверен, так как у вас нет сетевого уровня. OkHttp — это особый класс. Предоставляя его в качестве зависимости, вы тесно связываете несколько ваших классов с библиотекой OkHttp, которая имеет свою цену. В любом случае вы можете легко создать фиктивную версию с помощью mockito и предоставить ее своим классам при их создании для тестирования напрямую, а не через модуль. - person Itai Hanski; 14.02.2016