对于“常规的”Java项目,用mock/fake覆盖单元测试中的依赖关系是很容易的。您必须简单地构建Dagger组件并将其提供给驱动您的应用程序的“main”类。
For Android things is 不是那么简单的,我已经寻找了很长一段时间来寻找合适的例子,但我无法找到,所以我不得不创建自己的实现,我将非常感谢反馈这是使用Dagger2的正确方式,还是有更简单/更优雅的方法来覆盖依赖项。
这里是解释(project source can be found on github):
假设我们有一个简单的应用程序,它使用带有单个模块的单个Dagger组件的Dagge2,我们想要创建使用JUnit4、Mockito和Espresso的android单元测试
在MyApp
Application
类中,组件/注入器的初始化方式如下:
public class MyApp extends Application {
private MyDaggerComponent mInjector;
public void onCreate() {
super.onCreate();
initInjector();
}
protected void initInjector() {
mInjector = DaggerMyDaggerComponent.builder().httpModule(new HttpModule(new OkHttpClient())).build();
onInjectorInitialized(mInjector);
}
private void onInjectorInitialized(MyDaggerComponent inj) {
inj.inject(this);
}
public void externalInjectorInitialization(MyDaggerComponent injector) {
mInjector = injector;
onInjectorInitialized(injector);
}
...
在上面的代码中:正常的应用程序启动通过调用创建注入器的initInjector()
的onCreate()
,然后调用onInjectorInitialized()
。
为了从外部源(即单元测试) set
注入器,单元测试需要调用externalInjectorInitialization()
方法。
到现在为止还好。
让我们看看单元测试端的东西是什么样子的:
我们需要创建扩展MyApp类并用空方法覆盖initInjector
的MyTestApp调用,以避免创建双注入器(因为我们将在单元测试中创建一个新的注入器):
public class MyTestApp extends MyApp {
@Override
protected void initInjector() {
// empty
}
}
然后,我们必须以某种方式用MyTestApp替换原始的MyApp。这是通过自定义测试运行器完成的:
public class MyTestRunner extends AndroidJUnitRunner {
@Override
public Application newApplication(ClassLoader cl,
String className,
Context context) throws InstantiationException,
IllegalAccessException,
ClassNotFoundException {
return super.newApplication(cl, MyTestApp.class.getName(), context);
}
}
..。在newApplication()
中,我们有效地用测试类替换了原始的app类。
然后,我们必须告诉测试框架,我们已经拥有并希望使用我们的自定义测试运行器,因此在build.gradle中我们添加:
defaultConfig {
...
testInstrumentationRunner 'com.bolyartech.d2overrides.utils.MyTestRunner'
...
}
当运行单元测试时,我们的原始MyApp
将被替换为MyTestApp
。现在,我们必须创建组件/注入器,并将其与模拟/假冒一起提供给externalInjectorInitialization()
应用程序。为此,我们扩展了普通的ActivityTestRule:
@Rule
public ActivityTestRule<Act_Main> mActivityRule = new ActivityTestRule<Act_Main>(
Act_Main.class) {
@Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
OkHttpClient mockHttp = create mock OkHttpClient
MyDaggerComponent injector = DaggerMyDaggerComponent.
builder().httpModule(new HttpModule(mockHttp)).build();
MyApp app = (MyApp) InstrumentationRegistry.getInstrumentation().
getTargetContext().getApplicationContext();
app.externalInjectorInitialization(injector);
}
};
然后我们以通常的方式进行测试:
@Test
public void testHttpRequest() throws IOException {
onView(withId(R.id.btn_execute)).perform(click());
onView(withId(R.id.tv_result))
.check(matches(withText(EXPECTED_RESPONSE_BODY)));
}
上面的(模块)覆盖方法有效,但它需要为每个测试创建一个测试类,以便能够为每个测试提供单独的规则/(模拟设置)。我怀疑/猜测/希望有一种更简单、更优雅的方式。在那里吗?
这个方法很大程度上是基于@tomrozb for this question的答案。我只是添加了避免双重注入器创建的逻辑。
发布于 2016-03-04 01:24:59
1.注入过多依赖项
需要注意的两件事:
我所做的就是从我的测试用例中注入旧的依赖项。既然你的代码是干净的,所有的东西都被正确地限定了作用域,那么应该不会出什么差错,对吧?
下面的方法只有在你不依赖全局状态的情况下才会起作用,因为如果你在某个地方保留了对旧组件的引用,那么在运行时更改应用程序组件将不起作用。一旦您创建了下一个Activity
,它将获取新的应用程序组件,并提供您的测试依赖项。
此方法依赖于对作用域的正确处理。完成并重新启动活动应该重新创建其依赖项。因此,您可以在没有活动运行时或在启动新活动之前切换应用程序组件。
在测试用例中,只需在需要时创建组件
// in @Test or @Before, just inject 'over' the old state
App app = (App) InstrumentationRegistry.getTargetContext().getApplicationContext();
AppComponent component = DaggerAppComponent.builder()
.appModule(new AppModule(app))
.build();
component.inject(app);
如果您有一个如下所示的应用程序...
public class App extends Application {
@Inject
AppComponent mComponent;
@Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder().appModule(new AppModule(this)).build().inject(this);
}
}
...it将注入自身以及您在Application
中定义的任何其他依赖项。随后的任何调用都将获得新的依赖项。
2.使用不同的配置和应用程序
您可以选择与您的指令插入测试一起使用的配置:
android {
...
testBuildType "staging"
}
使用gradle资源合并,您可以选择为不同的构建类型使用多个不同版本的App
。
将Application
类从main
源文件夹移动到debug
和release
文件夹。Gradle将根据配置编译正确的源码集。然后,您可以根据需要修改应用程序的调试和发布版本。
如果您不希望使用不同的Application
类进行调试和发布,则可以创建另一个buildType
,仅用于您的检测测试。同样的原则也适用:将Application
类复制到每个源集文件夹,否则将收到编译错误。由于您需要在debug
和rlease
目录中拥有相同的类,因此可以创建另一个目录来包含用于调试和发布的类。然后将使用的目录添加到您的调试和发布源集。
发布于 2016-03-12 09:26:26
有一种更简单的方法可以做到这一点,即使是Dagger 2文档也提到了这一点,但它们并没有让它变得非常明显。以下是文档中的一段代码片段。
@Provides static Pump providePump(Thermosiphon pump) {
return pump;
}
热虹吸器执行泵,只要需要泵,Dagger就会注入热虹吸管。
回到你的例子。您可以使用静态布尔数据成员创建一个Module,它允许您在真实和模拟测试对象之间动态切换,如下所示。
@Module
public class HttpModule {
private static boolean isMockingHttp;
public HttpModule() {}
public static boolean mockHttp(boolean isMockingHttp) {
HttpModule.isMockingHttp = isMockingHttp;
}
@Provides
HttpClient providesHttpClient(OkHttpClient impl, MockHttpClient mockImpl) {
return HttpModule.isMockingHttp ? mockImpl : impl;
}
}
HttpClient可以是扩展的超类,也可以是由OkHttpClient和MockHttpClient实现的接口。Dagger将自动构造所需的类并注入它的内部依赖项,就像热虹吸管一样。
要模拟HttpClient,只需在将依赖项注入应用程序代码之前调用HttpModule.mockHttp(true)
即可。
这种方法的好处是:
https://stackoverflow.com/questions/35771356
复制相似问题