Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dagger 2 on production

Dagger 2 on production

Mirosław Stanek

October 17, 2015
Tweet

More Decks by Mirosław Stanek

Other Decks in Programming

Transcript

  1. on production
    Dagger 2

    View full-size slide

  2. @froger_mcs
    Head of mobile @ Azimo

    View full-size slide

  3. How to start with Dagger 2
    • The Future of Dependency Injection with Dagger 2
    (Jake Wharton)

    https://goo.gl/PPqT51
    • DAGGER 2 - A New Type of dependency injection
    (Gregory Kick)

    https://goo.gl/UVBvaH
    • Bunch of posts about Dagger 2 (DI, basics, scopes,
    performance)

    http://frogermcs.github.io/

    View full-size slide

  4. Dagger 2
    basic usage

    View full-size slide

  5. public class SplashActivity extends BaseActivity {


    private SplashActivityPresenter presenter;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);


    RestAdapter.Builder restAdapterBuilder = new RestAdapter.Builder()

    .setClient(new OkClient(MyOkHttpClient.getInstance()))

    .setEndpoint(getString(R.string.endpoint));

    RestAdapter restAdapter = restAdapterBuilder.build();


    GithubApiService githubApiService = restAdapter.create(GithubApiService.class);


    UserManager userManager = new UserManager(githubApiService);


    presenter = new SplashActivityPresenter(this, Validator.getInstance(), userManager);

    }

    }
    without DI/Dagger 2

    View full-size slide

  6. public class SplashActivity extends BaseActivity {


    @Inject

    SplashActivityPresenter presenter;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    getSplashActivityComponent().inject(this);

    }

    }
    with DI/Dagger 2

    View full-size slide

  7. public class SplashActivity extends BaseActivity {


    @Inject

    SplashActivityPresenter presenter;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    getSplashActivityComponent().inject(this);

    }

    }
    with DI/Dagger 2

    View full-size slide

  8. public class SplashActivity extends BaseActivity {


    @Inject

    SplashActivityPresenter presenter;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    getSplashActivityComponent().inject(this);

    }

    }
    No magic happens here
    Initialization takes place elsewhere
    with DI/Dagger 2

    View full-size slide

  9. Initialization/usage separation
    SplashActivityModule
    SplashActivity
    SplashActivityPresenter
    AppModule
    Application
    Validator
    ApiModule
    OkhttpClient
    RestAdapter
    GithubApiService
    UserManager
    SplashActivtity
    SplashActivityPresenter
    @Inject
    SplashActivtityPresenter
    SplashActivity
    Validator
    UserManager
    @Inject
    Initialization Usage

    View full-size slide

  10. @Module

    public class ApiModule {


    @Provides

    @Singleton

    OkHttpClient provideOkHttpClient() {

    OkHttpClient okHttpClient = new OkHttpClient();

    okHttpClient.setConnectTimeout(60 * 1000, TimeUnit.MILLISECONDS);

    okHttpClient.setReadTimeout(60 * 1000, TimeUnit.MILLISECONDS);

    return okHttpClient;

    }


    @Provides

    @Singleton

    RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {

    RestAdapter.Builder builder = new RestAdapter.Builder();

    builder.setClient(new OkClient(okHttpClient))

    .setEndpoint(application.getString(R.string.endpoint));

    return builder.build();

    }


    @Provides

    @Singleton

    GithubApiService provideGithubApiService(RestAdapter restAdapter) {

    return restAdapter.create(GithubApiService.class);

    }

    }
    @Module Example

    View full-size slide

  11. Injection process demystified

    View full-size slide

  12. SplashActivityModule
    SplashActivity
    SplashActivityPresenter
    SplashActivtity
    SplashActivityPresenter
    @Inject
    ?

    View full-size slide

  13. public class SplashActivity extends BaseActivity {


    @Inject

    SplashActivityPresenter presenter;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    getSplashActivityComponent().inject(this);

    }

    }
    DI with Dagger 2
    @Module

    public class SplashActivityModule {

    private SplashActivity splashActivity;


    public SplashActivityModule(SplashActivity splashActivity) {

    this.splashActivity = splashActivity;

    }


    @Provides

    @ActivityScope

    SplashActivity provideSplashActivity() {

    return splashActivity;

    }


    @Provides

    @ActivityScope

    SplashActivityPresenter

    provideSplashActivityPresenter(Validator validator, UserManager userManager) {

    return new SplashActivityPresenter(splashActivity, validator, userManager);

    }

    }
    ?

    View full-size slide

  14. SplashActivityModule
    SplashActivity
    SplashActivityPresenter
    SplashActivity
    Component
    SplashActivtity
    SplashActivityPresenter
    @Inject

    View full-size slide

  15. SplashActivityModule
    SplashActivity
    SplashActivityPresenter
    SplashActivity
    Component
    SplashActivtity
    SplashActivityPresenter
    @Inject
    @ActivityScope

    @Component(

    modules = SplashActivityModule.class,

    dependencies = AppComponent.class

    )

    public interface SplashActivityComponent {

    SplashActivity inject(SplashActivity splashActivity);

    }

    View full-size slide

  16. SplashActivityComponent
    SplashActivtity
    SplashActivityPresenter
    @Inject
    @Generated("dagger.internal.codegen.ComponentProcessor")

    public final class DaggerSplashActivityComponent

    implements SplashActivityComponent {

    private Provider getValidatorProvider;

    private Provider getUserManagerProvider;

    private Provider provideSplashActivityPresenterProvider;

    private MembersInjector splashActivityMembersInjector;


    private void initialize(final Builder builder) {

    //...

    }


    @Override

    public SplashActivity inject(SplashActivity splashActivity) {

    splashActivityMembersInjector.injectMembers(splashActivity);

    return splashActivity;

    }


    //...

    }

    View full-size slide

  17. MembersInjector
    SplashActivityComponent
    SplashActivtity
    SplashActivityPresenter
    @Inject
    @Generated("dagger.internal.codegen.ComponentProcessor")

    public final class SplashActivity_MembersInjector

    implements MembersInjector {

    private final MembersInjector supertypeInjector;

    private final Provider presenterProvider;


    //...


    @Override

    public void injectMembers(SplashActivity instance) {

    if (instance == null) {

    throw new NullPointerException(/*...*/);

    }

    supertypeInjector.injectMembers(instance);

    instance.presenter = presenterProvider.get();

    }


    //...

    }

    View full-size slide

  18. SplashActivityModule
    SplashActivity
    SplashActivityPresenter
    SplashActivity
    Component
    MembersInjector
    SplashActivtity
    SplashActivityPresenter
    @Inject
    ?

    View full-size slide

  19. Factory
    SplashActivityModule
    SplashActivity
    SplashActivityPresenter
    SplashActivity
    Component
    MembersInjector
    SplashActivtity
    SplashActivityPresenter
    @Inject

    View full-size slide

  20. Factory
    SplashActivity
    Component
    MembersInjector
    @Generated("dagger.internal.codegen.ComponentProcessor")

    public final class SplashActivityModule_SplashActivityPresenterFactory

    implements Factory {

    private final SplashActivityModule module;

    private final Provider validatorProvider;

    private final Provider userManagerProvider;


    //...


    @Override

    public SplashActivityPresenter get() {

    SplashActivityPresenter provided = module.splashActivityPresenter(

    validatorProvider.get(), userManagerProvider.get()

    );

    if (provided == null) {

    throw new NullPointerException(/*...*/);

    }

    return provided;

    }


    //...

    }

    View full-size slide

  21. Factory
    SplashActivityModule
    SplashActivity
    SplashActivityPresenter
    SplashActivity
    Component
    MembersInjector
    SplashActivtity
    SplashActivityPresenter
    @Inject
    Basic injection flow

    View full-size slide

  22. Alternative for scope instances
    or singletons
    ScopedProvider

    Factory
    SplashActivityModule
    SplashActivity
    SplashActivityPresenter
    SplashActivity
    Component
    MembersInjector
    SplashActivtity
    SplashActivityPresenter
    @Inject
    ScopedProvider

    SplashActivity
    Component
    MembersInjector
    SplashActivtity
    SplashActivityPresenter
    @Inject
    SplashActivityPresenter
    single instance
    1st inject
    2nd inject

    View full-size slide

  23. Injections interface

    View full-size slide

  24. SplashActivityModule
    SplashActivity
    SplashActivityPresenter
    SplashActivity
    Component
    SplashActivtity
    SplashActivityPresenter
    @Inject
    @ActivityScope

    @Component(

    modules = SplashActivityModule.class,

    dependencies = AppComponent.class

    )

    public interface SplashActivityComponent {

    SplashActivity inject(SplashActivity splashActivity);

    }

    View full-size slide

  25. @Singleton

    @Component(modules = {

    AzimoAppModule.class,

    //...

    })

    public interface AzimoAppComponent {

    void inject(AzimoApplication app);


    void inject(IntentUriHandlerActivity intentUriHandlerActivity);

    void inject(MyContactsActivity myContactsActivity);

    void inject(SelectItemActivity selectItemActivity);


    void inject(ProgressFragment progressFragment);


    void inject(GcmRegistrationService gcmRegistrationService);

    void inject(SyncConfigService syncConfigService);

    void inject(GcmMessagingService gcmMessagingService);


    void inject(LocaleChangeReceiver localeChangeReceiver);

    void inject(ReferrerReceiver referrerReceiver);

    void inject(ReminderReceiver reminderReceiver);


    void inject(RateSessionExpiredDialog rateSessionExpiredDialog);

    void inject(LeaveReviewDialog leaveReviewDialog);

    void inject(EnjoyingAppDialog enjoyingAppDialog);

    void inject(MakeSuggestionsDialog makeSuggestionsDialog);


    //…

    }
    Production app example

    View full-size slide

  26. What about base class
    injections?
    @Singleton

    @Component(modules = {

    AzimoAppModule.class,

    //...

    })

    public interface AzimoAppComponent {

    void inject(Application app);


    void inject(BaseActivity baseActivity);


    void inject(BaseFragment baseFragment);


    void inject(Service service);


    void inject(BroadcastReceiver broadcastReceiver);


    void inject(BaseDialogFragment baseDialogFragment);


    }

    View full-size slide

  27. public class BaseActivity extends AppCompatActivity {


    @Inject

    AnalyticsTracker analyticsTracker;


    }


    public class SplashActivity extends BaseActivity {


    @Inject

    Validator validator;


    }
    SplashActivtity
    SplashActivityPresenter
    @Inject
    BaseActivtity
    AnalyticsTracker
    @Inject

    View full-size slide

  28. public class BaseActivity extends AppCompatActivity {


    @Inject

    AnalyticsTracker analyticsTracker;


    }


    public class SplashActivity extends BaseActivity {


    @Inject

    Validator validator;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    getComponent().inject(this);

    }

    }
    Subclass injection

    View full-size slide

  29. @Generated("dagger.internal.codegen.ComponentProcessor")

    public final class SplashActivity_MembersInjector

    implements MembersInjector {

    private final MembersInjector supertypeInjector;

    private final Provider presenterProvider;


    //...


    @Override

    public void injectMembers(SplashActivity instance) {

    if (instance == null) {

    throw new NullPointerException(/*...*/);

    }

    supertypeInjector.injectMembers(instance);

    instance.presenter = presenterProvider.get();

    }


    //...

    }

    View full-size slide

  30. public class BaseActivity extends AppCompatActivity {


    @Inject

    AnalyticsTracker analyticsTracker;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    getComponent().inject(this);

    }

    }


    public class SplashActivity extends BaseActivity {


    @Inject

    Validator validator;


    }
    Super class injection

    View full-size slide

  31. Super class injection
    • ComponentReflectionInjector 

    https://gist.github.com/konmik/
    6ac725fa7134402539c4
    • Uses Reflection
    • Performance drawback is about 0.013 ms per
    injection on slow device

    View full-size slide

  32. • 0.013 ms per injection (50-methods component,
    tested on Samsung Galaxy S)
    • 60 FPS = 16ms per frame
    • => ~100 reflected injections per frame
    • (1000 direct injections per frame)
    ComponentReflectionInjector

    View full-size slide

  33. ComponentReflectionInjector
    public class BaseActivity extends AppCompatActivity {


    @Inject

    AnalyticsTracker analyticsTracker;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    componentReflectionInjector.inject(this);

    }

    }


    public class SplashActivity extends BaseActivity {


    @Inject

    Validator validator;


    }
    Super class injection

    View full-size slide

  34. But still no…
    @Singleton

    @Component(modules = {

    AzimoAppModule.class,

    //...

    })

    public interface AzimoAppComponent {

    void inject(Application app);


    void inject(BaseActivity baseActivity);


    void inject(BaseFragment baseFragment);


    void inject(Service service);


    void inject(BroadcastReceiver broadcastReceiver);


    void inject(BaseDialogFragment baseDialogFragment);


    }

    View full-size slide

  35. Injection types

    View full-size slide

  36. Direct injection
    public class SplashActivity {


    @Inject

    Validator validator;


    //...

    }
    SplashActivtity
    Validator

    View full-size slide

  37. Lazy injection
    public class SplashActivity {


    @Inject

    Lazy lazyValidator;


    //...

    }
    SplashActivtity
    Validator
    Lazy
    1st get() call
    2nd get() call
    get()
    SplashActivtity
    Dependencies
    Graph Validator
    Lazy

    View full-size slide

  38. Lazy injection
    public class SplashActivity {


    @Inject

    Lazy lazyValidator;


    //...

    }
    SplashActivtity
    Validator
    Lazy
    1st get() call
    nth get() call
    get()
    SplashActivtity
    Dependencies
    Graph Validator
    Lazy
    Same instance like
    in 1st call

    View full-size slide

  39. Launch time without lazy loading: 650ms
    SplashActivity
    Crashlytics
    200ms
    Mixpanel
    Google
    Analytics
    100ms
    Rest
    Client
    150ms 200ms
    SplashActivity
    Crashlytics
    200ms
    Mixpanel
    Google
    Analytics
    100ms
    Rest
    Client
    150ms 200ms
    Launch time with lazy loading: 100ms
    Those will be Lazy-loaded
    Lazy injection

    app launch time optimizations
    More optimization hints -> http://frogermcs.github.io/dagger-graph-creation-performance/

    View full-size slide

  40. Provider injection
    public class SplashActivity {


    @Inject

    Provider validatorProvider;


    //...

    }
    1st call
    2nd call
    get() (instance 1) SplashActivtity
    Dependencies
    Graph
    Validator
    Provider
    Validator
    Validator
    Provider
    get() (instance 2) SplashActivtity
    Dependencies
    Graph
    Validator

    View full-size slide

  41. Provider injection
    1st get() call
    nth get() call

    get() (instance 1) SplashActivtity
    Dependencies
    Graph
    Validator
    Provider
    Validator
    Validator
    Provider
    get() (instance n) SplashActivtity
    Dependencies
    Graph
    Validator
    New instance
    every time
    public class SplashActivity {


    @Inject

    Provider validatorProvider;


    //...

    }

    View full-size slide

  42. What about „singletons"?

    View full-size slide

  43. ScopedProvider

    Factory
    SplashActivityModule
    SplashActivity
    SplashActivityPresenter
    SplashActivity
    Component
    MembersInjector
    SplashActivtity
    SplashActivityPresenter
    @Inject

    View full-size slide

  44. @Generated("dagger.internal.codegen.ComponentProcessor")

    public final class SplashActivityModule_SplashActivityPresenterFactory

    implements Factory {

    private final SplashActivityModule module;

    private final Provider validatorProvider;

    private final Provider userManagerProvider;


    //...


    private void initialize(final Builder builder) {
    //…
    this.validatorProvider = Validator_Factory.create(/*...*/)
    this.userManagerProvider = ScopedProvider.create(
    UserManager_Factory.create(/*...*/)
    );
    //...
    }

    //...

    }

    View full-size slide

  45. @Generated("dagger.internal.codegen.ComponentProcessor")

    public final class SplashActivityModule_SplashActivityPresenterFactory

    implements Factory {

    private final SplashActivityModule module;

    private final Provider validatorProvider;

    private final Provider userManagerProvider;


    //...


    private void initialize(final Builder builder) {
    //…
    this.validatorProvider = Validator_Factory.create(/*...*/)
    this.userManagerProvider = ScopedProvider.create(
    UserManager_Factory.create(/*...*/)
    );
    //...
    }

    //...

    }

    View full-size slide

  46. ScopedProvider
    public final class ScopedProvider implements Provider {

    private static final Object UNINITIALIZED = new Object();

    private final Factory factory;

    private volatile Object instance = UNINITIALIZED;


    @Override

    public T get() {

    // double-check idiom from EJ2: Item 71

    Object result = instance;

    if (result == UNINITIALIZED) {

    synchronized (this) {

    result = instance;

    if (result == UNINITIALIZED) {

    instance = result = factory.get();

    }

    }

    }

    return (T) result;

    }
    //…

    }

    View full-size slide

  47. ScopedProvider
    public final class ScopedProvider implements Provider {

    private static final Object UNINITIALIZED = new Object();

    private final Factory factory;

    private volatile Object instance = UNINITIALIZED;


    @Override

    public T get() {

    // double-check idiom from EJ2: Item 71

    Object result = instance;

    if (result == UNINITIALIZED) {

    synchronized (this) {

    result = instance;

    if (result == UNINITIALIZED) {

    instance = result = factory.get();

    }

    }

    }

    return (T) result;

    }
    //…

    }

    View full-size slide

  48. @Singleton =/= singleton

    View full-size slide

  49. @Singleton == single instance 

    per component

    View full-size slide

  50. @ApplicationScope
    @UserScope
    @ActivityScope
    Scope types

    View full-size slide

  51. @ApplicationScope
    @UserScope
    @ActivityScope
    @Lorem
    @Ipsum
    @Dolor
    =
    It’s just a name. Not a difference at all.

    View full-size slide

  52. @Scope

    public @interface UserScope {

    }
    Scope definition

    View full-size slide

  53. @UserScope

    @Subcomponent(modules = UserModule.class)

    public interface UserComponent {


    }
    Scope usage
    @Module

    public class UserModule {


    @Provides

    @UserScope

    RepositoriesManager provideRepositoriesManager(User user, GithubApiService apiService) {

    return new RepositoriesManager(user, apiService);

    }

    }

    View full-size slide

  54. Scopes example

    View full-size slide

  55. Creating scopes
    • Subcomponents
    • Components dependencies

    View full-size slide

  56. @Singleton

    @Component(

    modules = {

    AppModule.class,

    GithubApiModule.class

    }

    )

    public interface AppComponent {


    }
    Singleton (Application scope)
    AppComponent
    - AppModule
    - GithubApiModule
    Creating scopes
    with subcomponents

    View full-size slide

  57. @Singleton

    @Component(

    modules = {

    AppModule.class,

    GithubApiModule.class

    }

    )

    public interface AppComponent {


    UserComponent plus(UserModule userModule);


    }
    UserScope
    UserComponent
    - UserModule
    Singleton (Application scope)
    AppComponent
    - AppModule
    - GithubApiModule
    @UserScope

    @Subcomponent(

    modules = {

    UserModule.class

    }

    )

    public interface UserComponent {


    }

    View full-size slide

  58. @UserScope

    @Subcomponent(

    modules = {

    UserModule.class

    }

    )

    public interface UserComponent {

    SplashActivityComponent plus(

    SplashActivityModule module

    );

    }
    ActivityScope
    ActivityComponent
    - ...ActivityModules
    UserScope
    UserComponent
    - UserModule
    Singleton (Application scope)
    AppComponent
    - AppModule
    - GithubApiModule

    @ActivityScope

    @Subcomponent(

    modules = SplashActivityModule.class

    )

    public interface SplashActivityComponent {


    }

    View full-size slide

  59. Creating scopes
    with components dependencies
    UserScope
    UserComponent
    - UserModule
    Singleton (Application scope)
    AppComponent
    - AppModule
    - GithubApiModule

    View full-size slide

  60. Creating scopes
    with components dependencies
    @Singleton

    @Component(

    modules = {

    AppModule.class,

    GithubApiModule.class

    }

    )

    public interface AppComponent {

    Validator Validator();

    UserManager getUserManager();

    AnalyticsManager getAnalyticsManager();

    GithubApiService getGithubApiService();

    }
    @UserScope

    @Component(

    modules = {

    UserModule.class

    },

    dependencies = AppComponent.class

    )

    public interface UserComponent {

    }

    View full-size slide

  61. Creating scopes
    with components dependencies
    @Singleton

    @Component(

    modules = {

    AppModule.class,

    GithubApiModule.class

    }

    )

    public interface AppComponent {

    Validator Validator();

    UserManager getUserManager();

    AnalyticsManager getAnalyticsManager();

    GithubApiService getGithubApiService();

    }
    @UserScope

    @Component(

    modules = {

    UserModule.class

    },

    dependencies = AppComponent.class

    )

    public interface UserComponent {

    }
    All dependencies used in dependent components
    have to be exposed

    View full-size slide

  62. Unit tests
    • Dagger 2 doesn't make unit testing easier
    • …but DI itself makes project ready for testing
    • (Still) no official way from Dagger 2 team
    • Dagger 2 + Robolectric 3

    View full-size slide

  63. Swapping modules
    public class TestGithubClientApplication

    extends GithubClientApplication {


    AppModule appModule;


    @Override

    public AppModule getAppModule() {

    if (appModule == null) {

    return super.getAppModule();

    }

    return appModule;

    }


    public void setAppModule(AppModule appModule) {

    this.appModule = appModule;

    initAppComponent();

    }

    }
    Dagger 2 + Robolectric 3
    public class GithubClientApplication
    extends Application {


    private AppComponent appComponent;


    @Override

    public void onCreate() {

    super.onCreate();

    initAppComponent();

    }


    protected void initAppComponent() {

    appComponent = DaggerAppComponent.builder()

    .appModule(getAppModule())

    .build();

    }

    @VisibleForTesting

    public AppModule getAppModule() {

    return new AppModule(this);

    }

    }
    App Tests

    View full-size slide

  64. Swapping modules
    @Module

    public class AppModule {

    private Application application;


    public AppModule(Application application) {

    this.application = application;

    }


    @Provides

    @Singleton

    public Application provideApplication() {

    return application;

    }


    @Provides

    @Singleton

    AnalyticsManager provideAnalyticsManager() {

    return new AnalyticsManager(application);

    }

    }
    Dagger 2 + Robolectric 3
    public class MockAppModule extends AppModule {

    @Mock

    AnalyticsManager analyticsManagerMock;


    public MockAppModule(Application application) {

    super(application);

    MockitoAnnotations.initMocks(this);

    }


    @Override

    AnalyticsManager provideAnalyticsManager() {

    return analyticsManagerMock;

    }

    }
    App Tests

    View full-size slide

  65. Swapping modules
    @RunWith(RobolectricGradleTestRunner.class)

    @Config(

    sdk = 18,

    constants = BuildConfig.class,

    application = TestGithubClientApplication.class

    )

    public class SplashActivityTests {


    @Before

    public void setup() {

    TestGithubClientApplication app = (TestGithubClientApplication) RuntimeEnvironment.application;

    MockAppModule mockAppModule = new MockAppModule(app);

    app.setAppModule(mockAppModule);

    }


    @Test

    public void testName() throws Exception {

    SplashActivity activity = Robolectric.setupActivity(SplashActivity.class);

    verify(activity.analyticsManager).logScreenView(anyString());

    }

    }
    Dagger 2 + Robolectric 3

    View full-size slide

  66. Injecting without Dagger
    public class TestGithubClientApplication

    extends GithubClientApplication {


    private AppComponent appComponent;

    private SplashActivityComponent splashActivityComponent;


    @Override

    public AppComponent getAppComponent() {

    if (appComponent == null) {

    appComponent = mock(AppComponent.class);

    when(appComponent.plus(any(SplashActivityModule.class)))

    .thenReturn(splashActivityComponent);

    }


    return appComponent;

    }


    public void setSplashActivityComponent(SplashActivityComponent component) {

    this.splashActivityComponent = component;

    }

    }

    View full-size slide

  67. @RunWith(RobolectricGradleTestRunner.class)

    @Config(

    sdk = 18,

    constants = BuildConfig.class,

    application = TestGithubClientApplication.class

    )

    public class SplashActivityTests {


    @Mock

    SplashActivityComponent splashActivityComponentMock;

    @Mock

    AnalyticsManager analyticsManagerMock;


    @Before

    public void setup() {

    MockitoAnnotations.initMocks(this);


    doAnswer(new Answer() {

    @Override

    public Object answer(InvocationOnMock invocation) {

    SplashActivity activity = (SplashActivity) invocation.getArguments()[0];

    activity.analyticsManager = analyticsManagerMock;

    return null;

    }

    }).when(splashActivityComponentMock).inject(any(SplashActivity.class));


    TestGithubClientApplication app = (TestGithubClientApplication) RuntimeEnvironment.application;

    app.setSplashActivityComponent(splashActivityComponentMock);

    }


    @Test

    public void testName() throws Exception {

    SplashActivity activity = Robolectric.setupActivity(SplashActivity.class);

    verify(activity.analyticsManager).logScreenView(anyString());

    }

    }

    View full-size slide

  68. @RunWith(RobolectricGradleTestRunner.class)

    @Config(

    sdk = 18,

    constants = BuildConfig.class,

    application = TestGithubClientApplication.class

    )

    public class SplashActivityTests {


    @Mock

    SplashActivityComponent splashActivityComponentMock;

    @Mock

    AnalyticsManager analyticsManagerMock;


    @Before

    public void setup() {

    MockitoAnnotations.initMocks(this);


    doAnswer(new Answer() {

    @Override

    public Object answer(InvocationOnMock invocation) {

    SplashActivity activity = (SplashActivity) invocation.getArguments()[0];

    activity.analyticsManager = analyticsManagerMock;

    return null;

    }

    }).when(splashActivityComponentMock).inject(any(SplashActivity.class));


    TestGithubClientApplication app = (TestGithubClientApplication) RuntimeEnvironment.application;

    app.setSplashActivityComponent(splashActivityComponentMock);

    }


    @Test

    public void testName() throws Exception {

    SplashActivity activity = Robolectric.setupActivity(SplashActivity.class);

    verify(activity.analyticsManager).logScreenView(anyString());

    }

    }

    View full-size slide

  69. Integration tests
    Espresso + Mockito + Dagger 2

    View full-size slide

  70. Do we really need
    dagger?

    View full-size slide

  71. Case #1
    (response 200)

    View full-size slide

  72. Testable
    Case #1
    (response 200)

    View full-size slide

  73. Case #2
    (response 400)

    View full-size slide

  74. Testable
    Case #2
    (response 400)

    View full-size slide

  75. Case #3
    (response 500)

    View full-size slide

  76. Not testable
    Case #3
    (response 500)

    View full-size slide

  77. Case #4
    (no internet connection)

    View full-size slide

  78. Not testable
    Case #4
    (no internet connection)

    View full-size slide

  79. Mock API client

    View full-size slide

  80. Instrumentation tests mocking
    public class ApplicationMock extends GithubClientApplication {


    private AppComponent appComponent;

    private GithubApiModule githubApiModuleMock;


    public void setGithubApiModuleMock(GithubApiModule githubApiModuleMock) {

    this.githubApiModuleMock = githubApiModuleMock;

    setupMockAppComponent();

    }


    public void setupMockAppComponent() {

    appComponent = DaggerAppComponent.builder()

    .appModule(new AppModule(this))

    .githubApiModule(githubApiModuleMock)

    .build();

    }


    @Override

    public AppComponent getAppComponent() {

    return appComponent == null ? super.getAppComponent() : appComponent;

    }

    }

    View full-size slide

  81. Instrumentation tests mocking
    @RunWith(AndroidJUnit4.class)

    public class SplashActivityUITests {


    @Mock UserManager userManagerMock;


    @Rule public ActivityTestRule activityRule = new ActivityTestRule<>(

    SplashActivity.class, true, false

    );


    @Before

    public void setUp() {

    MockitoAnnotations.initMocks(this);

    GithubApiModuleMock githubApiModuleMock = new GithubApiModuleMock(userManagerMock);

    ApplicationMock app = (ApplicationMock) InstrumentationRegistry.getTargetContext().getApplicationContext();

    app.setGithubApiModuleMock(githubApiModuleMock);

    activityRule.launchActivity(new Intent());

    }


    @Test

    public void checkLoadingError() {

    Mockito.when(userManagerMock.getUser(anyString()))

    .thenReturn(Observable.error(new RuntimeException("test")));


    onView(withId(R.id.etUsername)).perform(typeText("frogermcs"));

    onView(withId(R.id.btnShowRepositories)).perform(click());


    onView(withId(R.id.etUsername)).check(matches(hasErrorText("Validation error")));

    }

    }

    View full-size slide

  82. Instrumentation tests mocking
    @RunWith(AndroidJUnit4.class)

    public class SplashActivityUITests {


    @Mock UserManager userManagerMock;


    @Rule public ActivityTestRule activityRule = new ActivityTestRule<>(

    SplashActivity.class, true, false

    );


    @Before

    public void setUp() {

    MockitoAnnotations.initMocks(this);

    GithubApiModuleMock githubApiModuleMock = new GithubApiModuleMock(userManagerMock);

    ApplicationMock app = (ApplicationMock) InstrumentationRegistry.getTargetContext().getApplicationContext();

    app.setGithubApiModuleMock(githubApiModuleMock);

    activityRule.launchActivity(new Intent());

    }


    @Test

    public void checkLoadingError() {

    Mockito.when(userManagerMock.getUser(anyString()))

    .thenReturn(Observable.error(new RuntimeException("test")));


    onView(withId(R.id.etUsername)).perform(typeText("frogermcs"));

    onView(withId(R.id.btnShowRepositories)).perform(click());


    onView(withId(R.id.etUsername)).check(matches(hasErrorText("Validation error")));

    }

    }

    View full-size slide

  83. Example
    https://github.com/frogermcs/GithubClient
    Dagger 2, scopes, unit and instrumentation tests

    View full-size slide

  84. http://blog.codinghorror.com/learn-to-read-the-source-luke/
    „If you can't understand the platform below you,
    how can you understand your own software?”

    View full-size slide

  85. Thanks!
    @froger_mcs

    View full-size slide