2016-11-03 7 views
5

Я создал пример приложения (Да, это действительно просто пример и не имеет большого смысла, но хорош для понимания чистой архитектуры 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; 
} 
+0

Кинжал не используется для тестирования. Сначала вам нужно создать архитектуру своих классов, чтобы использовать DI, которые имеют хороший побочный эффект, чтобы класс легче тестировать с помощью подделок/mocks зависимостей. вы можете либо обновлять зависимости вручную, либо использовать насмешливую структуру. – Nkosi

+0

Моя архитектура уже готова к DI. Я просто не могу применить архитектуру к тестовому сценарию. Что именно вы подразумеваете под «новым образом зависимостей вручную»? – unlimited101

+0

вы создаете новый экземпляр «FakeMainInteractor» и вводите его в тестируемую систему при его создании. Также ваши подделки и конкретные реализации должны иметь общую абстракцию. – Nkosi

ответ

2

вы может написать метод сеттера в Application переопределить корень Component

Измените текущий 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; 
    } 
} 

теперь в вашем методе настройки тестирования Вы можете поменять реальный корень 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(); 
} 

Если вы используете зависимые подкомпоненты, вам, вероятно, придется снова написать метод setComponent внутри вашего BaseActivity. Обратите внимание, что добавление публичных геттеров и сеттеров может быть, в общем, плохой практикой проектирования OO, но в настоящее время это самое простое решение для выполнения герметичных тестов с использованием Dagger 2. Эти методы документированы here.

+1

Действительно ли 'setApplicationComponent()' действительно нужно возвращать 'ApplicationComponent 'или это должно быть" пусто "? –

+0

oops - хорошее пятно! Я исправлю это –

+0

@DavidRawson Спасибо, что помогает много. Но, к сожалению, тест по-прежнему использует «MainInteractor» вместо «FakeMainInteractor», хотя я реализовал и назвал эти методы «setComponent». Я думаю, что Компоненты перезаписываются TestComponents в «AndroidApplication» и «MainActivity», но все же оригинальные вставляются. Я приложил это новое редактирование к моему сообщению. – unlimited101