Я создал пример приложения (Да, это действительно просто пример и не имеет большого смысла, но хорош для понимания чистой архитектуры Android и инъекции зависимостей в кинжале 2). Мой код доступен на github. (Устаревшее см. this пост) Пример приложение просто позволяет вам ввести имя в EditText
и при нажатии на кнопку вы увидите сообщение «Hello ВашеИмя»Dagger 2 Injection Dependency в Android TestCase
У меня есть три различных Компоненты: ApplicationComponent
, ActivityComponent
и FragmentComponent
. FragmentComponent
содержит три модуля:
- ActivityModule
- FragmentModule
- InteractorModule
InteractorModule
обеспечивает MainInteractor
.
@Module
public class InteractorModule {
@Provides
@PerFragment
MainInteractor provideMainInteractor() {
return new MainInteractor();
}
}
В моей деятельности-UnitTest Я хочу, чтобы фальсифицировать MainInteractor
. Этот Interactor имеет только метод public Person createPerson(String name)
, который может создать объект Person. FakeMainInteractor
имеет тот же метод, но всегда создает объект Person с именем «Fake Person», независимо от параметра, который вы передали.
public class FakeMainInteractor {
public Person createPerson(final String name) {
return new Person("Fake Person");
}
}
Я уже создавал TestComponents для evey Компонент I, описанный выше. И в 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()
звонки 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
показывает «Привет, Джон».
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 Поддельный Person». Но я не знаю, как построить график зависимостей в тесте. Поэтому в тестовом режиме я хочу создать другой график, используя TestComponents и TestModules вместо исходных компонентов и модулей. Итак, как это сделать? Как использовать тест FakeMainInteractor
?
Как я уже сказал, я знаю, что это приложение не приносит ничего полезного. Но я хотел бы понять Testing with Dagger 2. Я уже прочитал статью this. Но он просто показывает, как сделать TestComponents и TestModules. Он не показывает, как использовать тестовый график в модульном тесте. Как это сделать? Может ли кто-нибудь предоставить примерный код?
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;
}
Кинжал не используется для тестирования. Сначала вам нужно создать архитектуру своих классов, чтобы использовать DI, которые имеют хороший побочный эффект, чтобы класс легче тестировать с помощью подделок/mocks зависимостей. вы можете либо обновлять зависимости вручную, либо использовать насмешливую структуру. – Nkosi
Моя архитектура уже готова к DI. Я просто не могу применить архитектуру к тестовому сценарию. Что именно вы подразумеваете под «новым образом зависимостей вручную»? – unlimited101
вы создаете новый экземпляр «FakeMainInteractor» и вводите его в тестируемую систему при его создании. Также ваши подделки и конкретные реализации должны иметь общую абстракцию. – Nkosi