Android Unit Tests with Dagger 2
我有一个使用Dagger 2进行依赖注入的Android应用。 我还在使用最新的gradle构建工具,该工具允许对单元测试和工具测试使用一个构建变体。 我在我的应用程序中使用
在我的主代码中,我在扩展
这是一些示例代码:
应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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; } } |
模块:
1 2 3 4 5 6 7 8 9 |
测试的基类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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; } } |
要么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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; } } |
测试模块:
1 2 3 4 5 6 7 8 9 |
如果没有一些解决方法,当前使用Dagger 2(从v2.0.0开始)是不可能的。你可以在这里读到它。
有关可能的解决方法的更多信息:
-
如何在Dagger 2.0的单元测试中覆盖模块/依赖关系?
-
使用Dagger2时创建测试依赖项
您已经说过了:
application's Component doesn't have inject methods for my test classes
因此,要解决此问题,我们可以对您的Application类进行测试。然后,我们可以为您的模块提供测试版本。为了使所有这些都能在测试中运行,我们可以使用Robolectric。
1)创建您的Application类的测试版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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()方法
1 2 3 4 5 6 7 8 9 10 | public class PipeGameApp extends Application { protected void initComponent() { DaggerPipeGameComponent.builder() .pipeGameModule(getPipeGameModule()) .build(); } protected PipeGameModule pipeGameModule() { return new PipeGameModule(this); }} |
3)您的PipeGameTestModule应该使用构造函数扩展生产模块:
1 2 3 4 | public class PipeGameTestModule extends PipeGameModule { public PipeGameTestModule(Application app) { super(app); }} |
4)现在,在junit test的setup()方法中,在测试应用程序上设置以下测试模块:
1 2 3 4 5 6 | @Before public void setup() { TestPipeGameApp app = (TestPipeGameApp) RuntimeEnvironment.application; PipeGameTestModule module = new PipeGameTestModule(app); app.setPipeGameModule(module); } |
现在,您可以按自己的意愿自定义测试模块。
我认为您可以通过从另一个角度看待这个问题。通过将Dock的模拟依赖项注入到受测试的构造类中,您可以轻松地对Dagger进行单元测试。
我的意思是说,在测试设置中,您可以:
- 模拟被测类的依赖
- 使用模拟的依赖项手动构造要测试的类
我们不需要测试依赖项是否正确注入,因为Dagger在编译过程中会验证依赖关系图的正确性。因此,编译失败将报告任何此类错误。这就是为什么可以接受在setup方法中手动创建被测类的原因。
在受测类中使用构造函数注入依赖项的代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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; } ... } |
使用受测类中的字段注入依赖项的代码示例(如果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
如果您将dagger2与Android一起使用,则可以使用应用程序风味来提供模拟资源。
有关模拟测试中的口味演示,请参见此处(无匕首):
该代码库有一个示例:
https://github.com/googlecodelabs/android-testing
在您的/src/prod/com/yourcompany/Component.java中
您提供生产组件。
在您的/src/mock/com/yourcompany/Component.java中
您提供了模拟组件。
这使您可以创建带有或不带有模拟的应用程序构建。
它还允许并行开发(一个团队的后端,另一个团队的前端应用程序),您可以进行模拟,直到api方法可用为止。
gradle命令的外观(其Makefile):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | install_mock: ./gradlew installMockDebug install: ./gradlew installProdDebug test_unit: ./gradlew testMockDebugUnitTest test_integration_mock: ./gradlew connectedMockDebugAndroidTest test_integration_prod: ./gradlew connectedProdDebugAndroidTest |
实际上,我遇到了同样的问题,找到了一个非常简单的解决方案。
我认为这不是最好的解决方案,但可以解决您的问题。
在您的应用模块中创建一个类似的类:
1 2 3 4 5 | public class ActivityTest<T extends ViewModelBase> { @Inject public T vm; } |
然后,在您的AppComponent中添加:
1 | void inject(ActivityTest<LoginFragmentVM> activityTest); |
然后,您可以将其注入您的测试课程中。
1 2 3 4 5 6 7 8 9 10 11 12 | 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(); } } |