依存性注入に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);
}
}
これは現在、Dagger 2(v2.0.0の時点)では回避策なしでは不可能です。それについて読むことができます こちら 。
考えられる回避策の詳細:
次のように言って、頭に釘を打ちました。
アプリケーションのコンポーネントにはテストクラス用の注入メソッドがありません
したがって、この問題を回避するために、アプリケーションクラスのテストバージョンを作成できます。その後、モジュールのテストバージョンを作成できます。そして、すべてをテストで実行するために、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);
}
これで、テストモジュールを元の方法でカスタマイズできます。
私の意見では、別の角度から見ればこの問題にアプローチできます。模擬の依存関係が挿入されたテスト対象の構築クラスの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() {}
...
}
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
私は実際に同じ問題を抱えていて、非常に簡単な解決策を見つけました。これは最良の解決策ではないと思いますが、問題は解決します。
アプリモジュールで同様のクラスを作成します。
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();
}
}