web-dev-qa-db-ja.com

Android Dagger 2を使用した単体テスト

依存性注入にDagger 2を使用するAndroidアプリがあります。ユニットテスト用のビルドバリアントとインストルメンテーションテスト用のビルドバリアントを許可する最新のgradleビルドツールも使用しています。Java.util.Randomを使用しています私のアプリは、テスト用にこれをモックしたいです。テストしているクラスは、Android stuffを使用しないため、通常のJava =クラス。

私のメインコードでは、Componentクラスを拡張するクラスでApplicationを定義していますが、単体テストではApplicationを使用していません。テストModuleおよびComponentを定義しようとしましたが、Daggerは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);
    }
}
50
Pikaling

これは現在、Dagger 2(v2.0.0の時点)では回避策なしでは不可能です。それについて読むことができます こちら

考えられる回避策の詳細:

25
tomrozb

次のように言って、頭に釘を打ちました。

アプリケーションのコンポーネントにはテストクラス用の注入メソッドがありません

したがって、この問題を回避するために、アプリケーションクラスのテストバージョンを作成できます。その後、モジュールのテストバージョンを作成できます。そして、すべてをテストで実行するために、Robolectricを使用できます。

1)アプリケーションクラスのテストバージョンを作成する

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)元のApplicationクラスには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)ここで、junitテストのsetup()メソッドで、テストアプリでこのテストモジュールを設定します。

@Before
public void setup() {
    TestPipeGameApp app = (TestPipeGameApp) RuntimeEnvironment.application;
    PipeGameTestModule module = new PipeGameTestModule(app);
    app.setPipeGameModule(module);
}

これで、テストモジュールを元の方法でカスタマイズできます。

6
IgorGanapolsky

私の意見では、別の角度から見ればこの問題にアプローチできます。模擬の依存関係が挿入されたテスト対象の構築クラスのDaggerに依存しないことで、クラスのユニットテストを簡単に実行できます。

私が言いたいのは、テストのセットアップで次のことができるということです。

  • テスト対象のクラスの依存関係をモックする
  • モックされた依存関係を使用して、テスト対象のクラスを手動で構築します

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

  ...
}
2
Praveer Gupta

Androidでdagger2を使用している場合、アプリのフレーバーを使用してモックリソースを提供できます。

モックテストのフレーバーのデモについては、こちらをご覧ください(短剣なし): 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で、モックコンポーネントを提供します。

これにより、モックの有無にかかわらず、アプリのビルドを作成できます。また、並行開発(あるチームによるバックエンド、別のチームによるフロントエンドアプリ)が可能になり、APIメソッドが利用可能になるまでモックできます。

私のgradleコマンドがどのように見えるか(Makefile):

install_mock:
    ./gradlew installMockDebug

install:
    ./gradlew installProdDebug

test_unit:
    ./gradlew testMockDebugUnitTest

test_integration_mock:
    ./gradlew connectedMockDebugAndroidTest

test_integration_prod:
    ./gradlew connectedProdDebugAndroidTest
1
TjerkW

私は実際に同じ問題を抱えていて、非常に簡単な解決策を見つけました。これは最良の解決策ではないと思いますが、問題は解決します。

アプリモジュールで同様のクラスを作成します。

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();
    }
}
0
Roy Ben Shabat