【发布时间】:2017-03-17 07:03:07
【问题描述】:
我已经构建了一个示例应用程序(是的,这实际上只是一个示例,没有多大意义,但有助于理解 Dagger 2 中的 Android 干净架构和依赖注入)。我的
代码在github 上可用。(已过时。请参阅this 帖子)示例应用程序只是让您在EditText 中输入名称,如果您按下按钮,您会看到一条消息“Hello YourName”
我有三个不同的组件:ApplicationComponent、ActivityComponent 和 FragmentComponent。 FragmentComponent 包含三个模块:
- 活动模块
- 片段模块
- 交互模块
InteractorModule 提供MainInteractor。
@Module
public class InteractorModule {
@Provides
@PerFragment
MainInteractor provideMainInteractor () {
return new MainInteractor();
}
}
在我的 Activity-UnitTest 中,我想伪造这个 MainInteractor。这个交互器只有一个方法public Person createPerson(String name),它可以创建一个Person对象。 FakeMainInteractor 具有相同的方法,但始终创建一个名为“Fake Person”的 Person 对象,与您传递的参数无关。
public class FakeMainInteractor {
public Person createPerson(final String name) {
return new Person("Fake Person");
}
}
我已经为上面描述的每一个组件创建了 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);
}
这个例子在非测试环境中运行良好。在MainActivity我有一个名为initializeInjector()的方法,我在其中构建FragmentComponent。 onCreate()calls 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);
}
}
这很好用。我的MainActivityTest也可以正常工作。它测试名称的输入和以下按钮单击的结果。但是TextView 显示“Hello John”。
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;
}
}
但正如我所说,我想使用 FakeMainInteractor 来打印“Hello Fake Person”。但我不知道如何在测试中建立依赖关系图。所以在测试模式下,我希望创建另一个图表,使用 TestComponents 和 TestModules 而不是原来的 Components 和 Modules。那么该怎么做呢?如何让测试使用FakeMainInteractor?
正如我所说,我知道这个示例应用程序没有做任何有用的事情。但我想了解Testing with Dagger 2。我已经阅读了this 文章。但它只是展示了如何制作 TestComponents 和测试模块。它没有说明如何在单元测试中使用测试图。怎么做?有人可以提供一些示例代码吗?
This 对我来说不是一个解决方案,因为它使用 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"))));
}
}
MainActivity拥有以下新方法:
@Override
public void setFragmentComponent(final FragmentComponent fragmentComponent) {
Log.w(TAG, "Only call this method to swap test doubles");
this.fragmentComponent = fragmentComponent;
}
AndroidApplication 拥有:
public void setApplicationComponent(ApplicationComponent applicationComponent) {
Log.w(TAG, "Only call this method to swap test doubles");
this.applicationComponent = applicationComponent;
}
【问题讨论】:
-
Dagger 不用于测试。您首先必须构建您的类以使用 DI,这具有使类更容易使用依赖项的假冒/模拟测试的良好副作用。您可以手动新建依赖项,也可以使用模拟框架。
-
我的架构已经为 DI 做好了准备。我只是无法将架构应用于测试用例。 “手动新建依赖项”到底是什么意思?
-
您创建一个
FakeMainInteractor的新实例,并在实例化它时将其注入被测系统。此外,您的赝品和具体实现应该共享一个共同的抽象。 -
@Nkosi 但你的意思是我创建
FakeMainInteractorjust 使用它的构造函数。而且“注入”不是指 Dagger 的注入,而只是将新对象应用于系统?
标签: java android unit-testing dependency-injection dagger-2