G. *_*ard 56 android dagger dagger-2
我有一个简单的Android活动,只有一个依赖项.我将依赖注入到活动中,onCreate
如下所示:
Dagger_HelloComponent.builder()
.helloModule(new HelloModule(this))
.build()
.initialize(this);
Run Code Online (Sandbox Code Playgroud)
在我的ActivityUnitTestCase
我要重写一个模拟的Mockito的依赖.我假设我需要使用提供模拟的特定于测试的模块,但我无法弄清楚如何将此模块添加到对象图中.
在Dagger 1.x中,这显然是用这样的东西完成的:
@Before
public void setUp() {
ObjectGraph.create(new TestModule()).inject(this);
}
Run Code Online (Sandbox Code Playgroud)
什么是Dagger 2.0相当于以上?
您可以在GitHub上看到我的项目及其单元测试.
tom*_*ozb 48
可能这更像是对测试模块覆盖的适当支持的解决方法,但它允许使用测试模块覆盖生产模块.下面的代码片段显示了只有一个组件和一个模块的简单情况,但这应适用于任何方案.它需要大量的样板和代码重复,所以要注意这一点.我相信将来会有更好的方法来实现这一目标.
我还创建了一个包含Espresso和Robolectric示例的项目.此答案基于项目中包含的代码.
解决方案需要两件事:
@Component
假设我们简单Application
如下:
public class App extends Application {
private AppComponent mAppComponent;
@Override
public void onCreate() {
super.onCreate();
mAppComponent = DaggerApp_AppComponent.create();
}
public AppComponent component() {
return mAppComponent;
}
@Singleton
@Component(modules = StringHolderModule.class)
public interface AppComponent {
void inject(MainActivity activity);
}
@Module
public static class StringHolderModule {
@Provides
StringHolder provideString() {
return new StringHolder("Release string");
}
}
}
Run Code Online (Sandbox Code Playgroud)
我们要在App
课堂上添加额外的方法.这允许我们替换生产组件.
/**
* Visible only for testing purposes.
*/
// @VisibleForTesting
public void setTestComponent(AppComponent appComponent) {
mAppComponent = appComponent;
}
Run Code Online (Sandbox Code Playgroud)
如您所见,该StringHolder
对象包含"Release string"值.这个对象被注入了MainActivity
.
public class MainActivity extends ActionBarActivity {
@Inject
StringHolder mStringHolder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
((App) getApplication()).component().inject(this);
}
}
Run Code Online (Sandbox Code Playgroud)
在我们的测试中,我们希望提供StringHolder
"测试字符串".我们要在创建App
之前在类中设置测试组件MainActivity
- 因为它StringHolder
是在onCreate
回调中注入的.
在Dagger v2.0.0中,组件可以扩展其他接口.我们可以利用它来创建我们的TestAppComponent
哪些扩展 AppComponent
.
@Component(modules = TestStringHolderModule.class)
interface TestAppComponent extends AppComponent {
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以定义我们的测试模块,例如TestStringHolderModule
.最后一步是使用先前在App
类中添加的setter方法设置测试组件.在创建活动之前执行此操作非常重要.
((App) application).setTestComponent(mTestAppComponent);
Run Code Online (Sandbox Code Playgroud)
浓咖啡
对于Espresso,我创建了自定义ActivityTestRule
,允许在创建活动之前交换组件.你可以在DaggerActivityTestRule
这里找到代码.
使用Espresso进行样品测试:
@RunWith(AndroidJUnit4.class)
@LargeTest
public class MainActivityEspressoTest {
public static final String TEST_STRING = "Test string";
private TestAppComponent mTestAppComponent;
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new DaggerActivityTestRule<>(MainActivity.class, new OnBeforeActivityLaunchedListener<MainActivity>() {
@Override
public void beforeActivityLaunched(@NonNull Application application, @NonNull MainActivity activity) {
mTestAppComponent = DaggerMainActivityEspressoTest_TestAppComponent.create();
((App) application).setTestComponent(mTestAppComponent);
}
});
@Component(modules = TestStringHolderModule.class)
interface TestAppComponent extends AppComponent {
}
@Module
static class TestStringHolderModule {
@Provides
StringHolder provideString() {
return new StringHolder(TEST_STRING);
}
}
@Test
public void checkSomething() {
// given
...
// when
onView(...)
// then
onView(...)
.check(...);
}
}
Run Code Online (Sandbox Code Playgroud)
Robolectric
Robolectric更容易归功于它RuntimeEnvironment.application
.
使用Robolectric进行样品测试:
@RunWith(RobolectricGradleTestRunner.class)
@Config(emulateSdk = 21, reportSdk = 21, constants = BuildConfig.class)
public class MainActivityRobolectricTest {
public static final String TEST_STRING = "Test string";
@Before
public void setTestComponent() {
AppComponent appComponent = DaggerMainActivityRobolectricTest_TestAppComponent.create();
((App) RuntimeEnvironment.application).setTestComponent(appComponent);
}
@Component(modules = TestStringHolderModule.class)
interface TestAppComponent extends AppComponent {
}
@Module
static class TestStringHolderModule {
@Provides
StringHolder provideString() {
return new StringHolder(TEST_STRING);
}
}
@Test
public void checkSomething() {
// given
MainActivity mainActivity = Robolectric.setupActivity(MainActivity.class);
// when
...
// then
assertThat(...)
}
}
Run Code Online (Sandbox Code Playgroud)
vau*_*oid 24
正如@EpicPandaForce正确地说,你无法扩展模块.然而,我想出了一个偷偷摸摸的解决方法,我认为避免了其他例子遭受的许多样板.
"扩展"模块的技巧是创建部分模拟,并模拟您要覆盖的提供者方法.
使用Mockito:
MyModule module = Mockito.spy(new MyModule());
Mockito.doReturn("mocked string").when(module).provideString();
MyComponent component = DaggerMyComponent.builder()
.myModule(module)
.build();
app.setComponent(component);
Run Code Online (Sandbox Code Playgroud)
我在这里创建了这个要点以展示一个完整的例子.
编辑
事实证明,即使没有部分模拟,你也可以做到这一点,如下所示:
MyComponent component = DaggerMyComponent.builder()
.myModule(new MyModule() {
@Override public String provideString() {
return "mocked string";
}
})
.build();
app.setComponent(component);
Run Code Online (Sandbox Code Playgroud)
yuv*_*val 10
@tomrozb提出的解决方法非常好,让我走上正轨,但我的问题是它暴露了setTestComponent()
PRODUCTION Application
类中的一个方法.我能够使这个工作略有不同,这样我的生产应用程序就不需要了解我的测试环境.
TL; DR - 使用测试组件和模块的测试应用程序扩展Application类.然后创建一个在测试应用程序而不是生产应用程序上运行的自定义测试运行器.
编辑:此方法仅适用于全局依赖项(通常标记为@Singleton
).如果您的应用程序具有不同范围的组件(例如,每个活动),那么您需要为每个范围创建子类,或者使用@ tomrozb的原始答案.感谢@tomrozb指出这一点!
此示例使用AndroidJUnitRunner测试运行器,但这可能适用于Robolectric和其他人.
首先,我的生产应用程序.它看起来像这样:
public class MyApp extends Application {
protected MyComponent component;
public void setComponent() {
component = DaggerMyComponent.builder()
.myModule(new MyModule())
.build();
component.inject(this);
}
public MyComponent getComponent() {
return component;
}
@Override
public void onCreate() {
super.onCreate();
setComponent();
}
}
Run Code Online (Sandbox Code Playgroud)
这样,我的活动和其他使用的类@Inject
只需调用类似于getApp().getComponent().inject(this);
将自己注入依赖图的东西.
为了完整性,这是我的组件:
@Singleton
@Component(modules = {MyModule.class})
public interface MyComponent {
void inject(MyApp app);
// other injects and getters
}
Run Code Online (Sandbox Code Playgroud)
我的模块:
@Module
public class MyModule {
// EDIT: This solution only works for global dependencies
@Provides @Singleton
public MyClass provideMyClass() { ... }
// ... other providers
}
Run Code Online (Sandbox Code Playgroud)
对于测试环境,请从生产组件中扩展测试组件.这与@ tomrozb的答案相同.
@Singleton
@Component(modules = {MyTestModule.class})
public interface MyTestComponent extends MyComponent {
// more component methods if necessary
}
Run Code Online (Sandbox Code Playgroud)
测试模块可以是你想要的任何东西.大概你会在这里处理你的嘲笑和事情(我使用Mockito).
@Module
public class MyTestModule {
// EDIT: This solution only works for global dependencies
@Provides @Singleton
public MyClass provideMyClass() { ... }
// Make sure to implement all the same methods here that are in MyModule,
// even though it's not an override.
}
Run Code Online (Sandbox Code Playgroud)
所以,现在,棘手的部分.创建一个从生产应用程序类扩展的测试应用程序类,并覆盖该setComponent()
方法以使用测试模块设置测试组件.请注意,这只有在MyTestComponent
是后代的情况下才有效MyComponent
.
public class MyTestApp extends MyApp {
// Make sure to call this method during setup of your tests!
@Override
public void setComponent() {
component = DaggerMyTestComponent.builder()
.myTestModule(new MyTestModule())
.build();
component.inject(this)
}
}
Run Code Online (Sandbox Code Playgroud)
确保setComponent()
在开始测试之前调用应用程序以确保图表设置正确.像这样的东西:
@Before
public void setUp() {
MyTestApp app = (MyTestApp) getInstrumentation().getTargetContext().getApplicationContext();
app.setComponent()
((MyTestComponent) app.getComponent()).inject(this)
}
Run Code Online (Sandbox Code Playgroud)
最后,最后一个缺失的部分是使用自定义测试运行器覆盖TestRunner.在我的项目中,我正在使用AndroidJUnitRunner
它,但看起来你可以用Robolectric做同样的事情.
public class TestRunner extends AndroidJUnitRunner {
@Override
public Application newApplication(@NonNull ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return super.newApplication(cl, MyTestApp.class.getName(), context);
}
}
Run Code Online (Sandbox Code Playgroud)
您还必须更新您的testInstrumentationRunner
gradle,如下所示:
testInstrumentationRunner "com.mypackage.TestRunner"
Run Code Online (Sandbox Code Playgroud)
如果您使用的是Android Studio,则还必须从运行菜单中单击"编辑配置",然后在"特定仪器运行器"下输入测试运行器的名称.
就是这样!希望这些信息有助于某人:)