Подтвердить что ты не робот

Тесты для Android с помощью кинжала 2

У меня есть приложение для 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);
    }
}
4b9b3361

Ответ 1

В настоящее время это невозможно с кинжалом 2 (начиная с версии 2.0) без каких-либо обходных решений. Вы можете прочитать об этом здесь.

Подробнее о возможных обходных решениях:

Ответ 2

Вы ударили ноготь по голове, сказав:

Компонент не имеет методов ввода для моих тестовых классов

Итак, чтобы обойти эту проблему, мы можем сделать тестовую версию вашего класса 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);
}

Теперь вы можете настроить свой тестовый модуль так, как вам захотелось.

Ответ 3

По моему мнению, вы можете подойти к этой проблеме, взглянув на нее под другим углом. Вы легко сможете 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() {}

  ...
}

Ответ 4

Если вы используете 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

Ответ 5

На самом деле у меня была такая же проблема, и я нашел очень простое решение. Это не лучшее возможное решение, я думаю, но оно решит вашу проблему.

Создайте аналогичный класс в вашем модуле приложения:

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