Ответ 1
В настоящее время это невозможно с кинжалом 2 (начиная с версии 2.0) без каких-либо обходных решений. Вы можете прочитать об этом здесь.
Подробнее о возможных обходных решениях:
У меня есть приложение для Android, которое использует Dagger 2 для инъекции зависимостей. Я также использую новейшие инструменты сборки gradle, которые позволяют вариант сборки для модульного тестирования и один для контрольных тестов. Я использую java.util.Random
в своем приложении, и я хочу издеваться над этим для тестирования. В классах, которые я тестирую, не используются какие-либо элементы Android, поэтому они просто обычные классы Java.
В моем основном коде я определяю Component
в классе, который расширяет класс Application
, но в модульных тестах я не использую Application
. Я попытался определить тест Module
и Component
, но Кинжал не будет генерировать Component
. Я также попытался использовать Component
, который я определил в своем приложении, и обмениваю Module
при его создании, но приложение Component
не имеет методов inject
для моих тестовых классов. Как я могу обеспечить макетную реализацию Random
для тестирования?
Вот пример кода:
Применение:
public class PipeGameApplication extends Application {
private PipeGame pipeGame;
@Singleton
@Component(modules = PipeGameModule.class)
public interface PipeGame {
void inject(BoardFragment boardFragment);
void inject(ConveyorFragment conveyorFragment);
}
@Override
public void onCreate() {
super.onCreate();
pipeGame = DaggerPipeGameApplication_PipeGame.create();
}
public PipeGame component() {
return pipeGame;
}
}
Модуль:
@Module
public class PipeGameModule {
@Provides
@Singleton
Random provideRandom() {
return new Random();
}
}
Базовый класс для тестов:
public class BaseModelTest {
PipeGameTest pipeGameTest;
@Singleton
@Component(modules = PipeGameTestModule.class)
public interface PipeGameTest {
void inject(BoardModelTest boardModelTest);
void inject(ConveyorModelTest conveyorModelTest);
}
@Before
public void setUp() {
pipeGameTest = DaggerBaseModelTest_PipeGameTest.create(); // Doesn't work
}
public PipeGameTest component() {
return pipeGameTest;
}
}
или
public class BaseModelTest {
PipeGameApplication.PipeGame pipeGameTest;
// This works if I make the test module extend
// the prod module, but it can't inject my test classes
@Before
public void setUp() {
pipeGameTest = DaggerPipeGameApplication_PipeGame.builder().pipeGameModule(new PipeGameModuleTest()).build();
}
public PipeGameApplication.PipeGame component() {
return pipeGameTest;
}
}
Модуль тестирования:
@Module
public class PipeGameTestModule {
@Provides
@Singleton
Random provideRandom() {
return mock(Random.class);
}
}
В настоящее время это невозможно с кинжалом 2 (начиная с версии 2.0) без каких-либо обходных решений. Вы можете прочитать об этом здесь.
Подробнее о возможных обходных решениях:
Вы ударили ноготь по голове, сказав:
Компонент не имеет методов ввода для моих тестовых классов
Итак, чтобы обойти эту проблему, мы можем сделать тестовую версию вашего класса Application. Тогда у нас может быть тестовая версия вашего модуля. И чтобы все это выполнялось в тесте, мы можем использовать Robolectric.
1) Создайте тестовую версию класса Application
public class TestPipeGameApp extends PipeGameApp {
private PipeGameModule pipeGameModule;
@Override protected PipeGameModule getPipeGameModule() {
if (pipeGameModule == null) {
return super.pipeGameModule();
}
return pipeGameModule;
}
public void setPipeGameModule(PipeGameModule pipeGameModule) {
this.pipeGameModule = pipeGameModule;
initComponent();
}}
2) В вашем исходном классе приложения должны быть методы initComponent() и pipeGameModule()
public class PipeGameApp extends Application {
protected void initComponent() {
DaggerPipeGameComponent.builder()
.pipeGameModule(getPipeGameModule())
.build();
}
protected PipeGameModule pipeGameModule() {
return new PipeGameModule(this);
}}
3) Ваш PipeGameTestModule должен расширять производственный модуль конструктором:
public class PipeGameTestModule extends PipeGameModule {
public PipeGameTestModule(Application app) {
super(app);
}}
4) Теперь, в вашем методе setup() junit, установите этот тестовый модуль в тестовом приложении:
@Before
public void setup() {
TestPipeGameApp app = (TestPipeGameApp) RuntimeEnvironment.application;
PipeGameTestModule module = new PipeGameTestModule(app);
app.setPipeGameModule(module);
}
Теперь вы можете настроить свой тестовый модуль так, как вам захотелось.
По моему мнению, вы можете подойти к этой проблеме, взглянув на нее под другим углом. Вы легко сможете unit test ваш класс, не завися от кинжала для тестируемого строительного класса с его издеваемыми зависимостями, введенными в него.
Я хочу сказать, что в тестовой настройке вы можете:
Нам не нужно проверять правильность ввода зависимостей, поскольку Dagger проверяет правильность графика зависимостей во время компиляции. Таким образом, любые ошибки будут сообщаться в результате сбоя компиляции. Вот почему должно быть приемлемо ручное создание тестируемого класса в методе установки.
Пример кода, в котором зависимость вводится с использованием конструктора в тестируемом классе:
public class BoardModelTest {
private BoardModel boardModel;
private Random random;
@Before
public void setUp() {
random = mock(Random.class);
boardModel = new BoardModel(random);
}
@Test
...
}
public class BoardModel {
private Random random;
@Inject
public BoardModel(Random random) {
this.random = random;
}
...
}
Пример кода, в котором зависимость вводится с использованием поля в тестируемом классе (в случае, если BoardModel
создается каркасом):
public class BoardModelTest {
private BoardModel boardModel;
private Random random;
@Before
public void setUp() {
random = mock(Random.class);
boardModel = new BoardModel();
boardModel.random = random;
}
@Test
...
}
public class BoardModel {
@Inject
Random random;
public BoardModel() {}
...
}
Если вы используете dagger2 с Android, вы можете использовать App-аффрабы для предоставления насмешливых ресурсов.
См. здесь демонстрацию вкусов при макетном тестировании (без кинжала): https://www.youtube.com/watch?v=vdasFFfXKOY
В этой кодовой базе есть пример: https://github.com/googlecodelabs/android-testing
В /src/prod/com/yourcompany/Component.java вы предоставляете свои производственные компоненты.
В /src/mock/com/yourcompany/Component.java вы предоставляете свои издевательские компоненты.
Это позволяет создавать сборки вашего приложения с или без насмешек. Он также позволяет параллельную разработку (поддержка одной команды, приложение frontend другой командой), вы можете высмеивать до тех пор, пока не будут доступны api-методы.
Как выглядят мои команды gradle (его Makefile):
install_mock:
./gradlew installMockDebug
install:
./gradlew installProdDebug
test_unit:
./gradlew testMockDebugUnitTest
test_integration_mock:
./gradlew connectedMockDebugAndroidTest
test_integration_prod:
./gradlew connectedProdDebugAndroidTest
На самом деле у меня была такая же проблема, и я нашел очень простое решение. Это не лучшее возможное решение, я думаю, но оно решит вашу проблему.
Создайте аналогичный класс в вашем модуле приложения:
public class ActivityTest<T extends ViewModelBase> {
@Inject
public T vm;
}
Затем в приложении AppComponent добавьте:
void inject(ActivityTest<LoginFragmentVM> activityTest);
Затем вы сможете ввести это в свой тестовый класс.
public class HelloWorldEspressoTest extends ActivityTest<LoginFragmentVM> {
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class);
@Test
public void listGoesOverTheFold() throws InterruptedException {
App.getComponent().inject(this);
vm.email.set("1234");
closeSoftKeyboard();
}
}