Android TestCase中的Dagger 2依赖注入 [英] Dagger 2 Dependency Injection in Android TestCase
问题描述
我已经构建了一个示例应用程序(是的,它实际上只是一个示例,并没有多大意义,但有助于理解Dagger 2中的Android干净架构和依赖注入)。我的
代码可在 github 上找到。(已过时。请参阅this 帖子)示例应用程序只是让你在 EditText
中键入一个名字,如果按下按钮,你会看到一条消息Hello YourName
I have built up an example application (Yes, it is really just an example and doesn’t make much sense but is good for understanding Android clean architecture and dependency injection in Dagger 2). My
code is available on github.(Outdated. See this post) The example app just let’s you type in a name in an EditText
and if you press the Button you see a message "Hello YourName"
我有三个不同的组件: ApplicationComponent
, ActivityComponent
和 FragmentComponent
。 FragmentComponent
包含三个模块:
I have three different Components: ApplicationComponent
, ActivityComponent
and FragmentComponent
. FragmentComponent
contains three modules:
- ActivityModule
- FragmentModule
- InteractorModule
InteractorModule
提供 MainInteractor
。
@Module
public class InteractorModule {
@Provides
@PerFragment
MainInteractor provideMainInteractor () {
return new MainInteractor();
}
}
在我的Activity-UnitTest中我想伪造这个 MainInteractor
。这个Interactor只有一个方法 public Person createPerson(String name)
,它可以创建一个Person对象。 FakeMainInteractor
具有相同的方法,但始终创建一个名为假人的Person对象,与您传递的参数无关。
In my Activity-UnitTest I want to fake this MainInteractor
. This Interactor just has a method public Person createPerson(String name)
which can create a Person object. The FakeMainInteractor
has the same method but always creates a Person object with the name „Fake Person", indepent of the parameter you have passed.
public class FakeMainInteractor {
public Person createPerson(final String name) {
return new Person("Fake Person");
}
}
我已经为上面描述的evey组件I创建了TestComponents。并在 TestFragmentComponent
我用 TestInteractorModule
交换了 InteractorModule
。
I already createt TestComponents for evey Component I described above. And In TestFragmentComponent
I swapped InteractorModule
with 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()
调用 onActivitySetup()
哪个
调用 initializeInjector()
和 inject()
。
This example is running well in a non-test context. In the MainActivity
I have a method called initializeInjector()
where I build the FragmentComponent
. And onCreate()
calls onActivitySetup()
which
calls initializeInjector()
and 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。
This works fine. And my MainActivityTest
also works fine. It tests the typing in of the name and the result of the following button click. But the TextView
shows „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。但我不知道如何在Test中构建依赖图。因此,在测试模式下,我想要使用TestComponents和TestModules而不是原始的组件和模块创建另一个图形。那怎么办呢?如何让测试使用 FakeMainInteractor
?
But as I told I want to use the FakeMainInteractor
which would print „Hello Fake Person". But I don’t know how to build up the dependency graph within the Test. So in test mode I want another graph to be created, using the TestComponents and TestModules instead of the original Components and Modules. So how to do that? How to let the test use FakeMainInteractor
?
正如我所说,我知道这个示例应用程序什么都不做有用。但我想了解Testing with Dagger 2.我已经阅读了这篇文章。但它只是展示了如何制作TestComponents
和TestModules。它没有说明如何在单元测试中使用测试图。怎么做?有人可以提供一些示例代码吗?
As I told, I know this example app doesn’t do anything useful. But I would like to understand Testing with Dagger 2. I already read this article. But it just shows how to make the TestComponents and the TestModules. It does not tell how to use a Test-Graph in the Unit Test. How to do that? Can someone provide some example code?
这个对我来说不是一个解决方案,因为它使用的是旧版本的Dagger 2(我使用的是2.7版本),并且不描述了如何连接TestComponents。
This is not a solution for me, because it uses and older version of Dagger 2 (I use version 2.7) and it does not describe how to wire the TestComponents.
在尝试@DavidRawson后,我的一些课程改变了他们的实施:
After trying approach by @DavidRawson some of my classes changed their implementation:
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;
}
推荐答案
你可以编写一个setter方法在应用程序
中覆盖根组件
You can write a setter method in the Application
to override the root Component
修改通过添加此方法,您当前的应用程序
类:
Modify your current Application
class by adding this method:
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;
}
}
现在在您的测试设置方法中,您可以交换真实的根组件
与假的:
now in your test setup method, you can swap the real root Component
with the fake one:
@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();
}
如果您使用的是从属子组件,您可能需要再次写在 BaseActivity
中的一个名为 setComponent
的方法。请注意,添加公共getter和setter通常可能是糟糕的OO设计实践,但这是目前使用Dagger 2执行密封测试的最简单的解决方案。这些方法记录在案这里。
If you are using dependent subcomponents, you will probably have to, again, write a method called setComponent
inside your BaseActivity
. Please note that adding public getters and setters can be, in general, bad OO design practice but this is currently the simplest solution for performing hermetic tests using Dagger 2. These methods are documented here.
这篇关于Android TestCase中的Dagger 2依赖注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!