unl*_*101 5 java android unit-testing dependency-injection dagger-2
我已经构建了一个示例应用程序(是的,它实际上只是一个示例,并没有多大意义,但有助于理解Dagger 2中的Android干净架构和依赖注入).我的代码可以在github上找到.(过时.看到这篇文章)示例应用程序只是让你输入一个名称EditText,如果按下按钮,你会看到一条消息"Hello YourName"
我有三个不同的组件:ApplicationComponent,ActivityComponent和FragmentComponent.FragmentComponent包含三个模块:
InteractorModule提供了一个MainInteractor.
@Module
public class InteractorModule {
@Provides
@PerFragment
MainInteractor provideMainInteractor () {
return new MainInteractor();
}
}
Run Code Online (Sandbox Code Playgroud)
在我的Activity-UnitTest中,我想假装这个MainInteractor.这个Interactor只有一个public Person createPerson(String name)可以创建Person对象的方法.在FakeMainInteractor具有相同的方法,但总是创建了一个名为"冒充者" Person对象,你传递的参数了独立的.
public class FakeMainInteractor {
public Person createPerson(final String name) {
return new Person("Fake Person");
}
}
Run Code Online (Sandbox Code Playgroud)
我已经为上面描述的evey组件I创建了TestComponents.在TestFragmentComponent我换InteractorModule了TestInteractorModule.
@PerFragment
@Component(dependencies = TestApplicationComponent.class, modules = {ActivityModule.class, FragmentModule.class, TestInteractorModule.class})
public interface TestFragmentComponent {
void inject(MainFragment mainFragment);
void inject(MainActivity mainActivity);
}
Run Code Online (Sandbox Code Playgroud)
此示例在非测试上下文中运行良好.在MainActivity我有一个方法调用initializeInjector()我建立的地方FragmentComponent.并onCreate()调用onActivitySetup()它调用initializeInjector()和inject().
public class MainActivity extends BaseActivity implements MainFragment.OnFragmentInteractionListener,
HasComponent<FragmentComponent> {
private FragmentComponent fragmentComponent;
private Fragment currentFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
currentFragment = new MainFragment();
addFragment(R.id.fragmentContainer, currentFragment);
}
}
private void initializeInjector() {
this.fragmentComponent = DaggerFragmentComponent.builder()
.applicationComponent(getApplicationComponent())
.activityModule(getActivityModule())
.fragmentModule(getFragmentModule())
.build();
}
@Override
protected void onActivitySetup() {
this.initializeInjector();
fragmentComponent.inject(this);
}
@Override
public void onFragmentInteraction(final Uri uri) {
}
@Override public FragmentComponent getComponent() {
return fragmentComponent;
}
public FragmentModule getFragmentModule() {
return new FragmentModule(currentFragment);
}
}
Run Code Online (Sandbox Code Playgroud)
这很好用.我的工作MainActivityTest也很好.它测试名称的输入和以下按钮单击的结果.但TextView节目"你好约翰".
public class MainActivityTest implements HasComponent<TestFragmentComponent> {
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class, true, true);
private MainActivity mActivity;
private TestFragmentComponent mTestFragmentComponent;
@Before
public void setUp() throws Exception {
mActivity = mActivityRule.getActivity();
}
@Test
public void testMainFragmentLoaded() throws Exception {
mActivity = mActivityRule.getActivity();
assertTrue(mActivity.getCurrentFragment() instanceof MainFragment);
}
@Test
public void testOnClick() throws Exception {
onView(withId(R.id.edittext)).perform(typeText("John"));
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.textview_greeting)).check(matches(withText(containsString("Hello John"))));
}
@Override
public TestFragmentComponent getComponent() {
return mTestFragmentComponent;
}
}
Run Code Online (Sandbox Code Playgroud)
但正如我告诉我想用的FakeMainInteractor那样会打印出"Hello Fake Person".但我不知道如何在Test中构建依赖图.所以在测试模式下,我希望使用TestComponents和TestModules而不是原始的组件和模块来创建另一个图.那怎么办呢?如何让测试使用FakeMainInteractor?
正如我所说,我知道这个示例应用程序没有做任何有用的事情.但我想了解用Dagger 2测试.我已经读过这篇文章了.但它只是展示了如何制作TestComponents和TestModules.它没有说明如何在单元测试中使用测试图.怎么做?有人可以提供一些示例代码吗?
这对我来说不是一个解决方案,因为它使用的是Dagger 2的旧版本(我使用的是2.7版本),并没有描述如何连接TestComponents.
在尝试了@DavidRawson之后,我的一些类改变了它们的实现:
public class MainActivityTest{
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class, true, true);
private MainActivity mActivity;
private TestApplicationComponent mTestApplicationComponent;
private TestFragmentComponent mTestFragmentComponent;
private void initializeInjector() {
mTestApplicationComponent = DaggerTestApplicationComponent.builder()
.applicationModule(new ApplicationModule(getApp()))
.build();
getApp().setApplicationComponent(mTestApplicationComponent);
mTestFragmentComponent = DaggerTestFragmentComponent.builder()
.testApplicationComponent(mTestApplicationComponent)
.activityModule(mActivity.getActivityModule())
.testInteractorModule(new TestInteractorModule())
.build();
mActivity.setFragmentComponent(mTestFragmentComponent);
mTestApplicationComponent.inject(this);
mTestFragmentComponent.inject(this);
}
public AndroidApplication getApp() {
return (AndroidApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
}
@Before
public void setUp() throws Exception {
mActivity = mActivityRule.getActivity();
initializeInjector();
}
@Test
public void testMainFragmentLoaded() throws Exception {
mActivity = mActivityRule.getActivity();
assertTrue(mActivity.getCurrentFragment() instanceof MainFragment);
}
@Test
public void testOnClick() throws Exception {
onView(withId(R.id.edittext)).perform(typeText("John"));
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.textview_greeting)).check(matches(withText(containsString("Hello John"))));
}
}
Run Code Online (Sandbox Code Playgroud)
MainActivity拥有以下新方法:
@Override
public void setFragmentComponent(final FragmentComponent fragmentComponent) {
Log.w(TAG, "Only call this method to swap test doubles");
this.fragmentComponent = fragmentComponent;
}
Run Code Online (Sandbox Code Playgroud)
AndroidApplication 旗下拥有:
public void setApplicationComponent(ApplicationComponent applicationComponent) {
Log.w(TAG, "Only call this method to swap test doubles");
this.applicationComponent = applicationComponent;
}
Run Code Online (Sandbox Code Playgroud)
您可以在 中编写一个 setter 方法来Application覆盖 rootComponent
通过添加以下方法修改当前Application类:
public class AndroidApplication extends Application {
@VisibleForTesting
public void setApplicationComponent(ApplicationComponent applicationComponent) {
Log.w(TAG, "Only call this method to swap test doubles");
this.applicationComponent = applicationComponent;
}
}
Run Code Online (Sandbox Code Playgroud)
现在在您的测试设置方法中,您可以将真实根Component与假根交换:
@Before
public void setUp() throws Exception {
TestApplicationComponent component =
DaggerTestApplicationComponent.builder()
.applicationModule(new TestApplicationModule()).build();
getApp().setComponent(component);
}
private AndroidApplication getApp() {
return (AndroidApplication) InstrumentationRegistry.getInstrumentation()
.getTargetContext().getApplicationContext();
}
Run Code Online (Sandbox Code Playgroud)
如果您使用依赖子组件,您可能必须再次编写一个setComponent在BaseActivity. 请注意,添加公共 getter 和 setter 通常可能是糟糕的 OO 设计实践,但这是目前使用 Dagger 2 执行密封测试的最简单的解决方案。这些方法记录在此处。