Testable Android Apps
Fabio Collini
DroidCon Italy – Torino – April 2015 – @fabioCollini 2
Fabio Collini
@fabioCollini
linkedin.com/in/fabiocollini
Folder Organizer
cosenonjaviste.it
nana bianca
Freapp
instal.com
Rain tomorrow?
DroidCon Italy – Torino – April 2015 – @fabioCollini 3
Quick survey
Do you write automated tests?
DroidCon Italy – Torino – April 2015 – @fabioCollini 4
Return of Investment - ROI
Net profit
Investment
DroidCon Italy – Torino – April 2015 – @fabioCollini 5
Agenda
1. Legacy code
2. Dependency Injection
3. Mockito
4. Dagger
5. Dagger & Android
6. Model View Presenter
DroidCon Italy – Torino – April 2015 – @fabioCollini 6
TestableAndroidAppsDroidCon15
https://github.com/fabioCollini/
TestableAndroidAppsDroidCon15
DroidCon Italy - Torino - April 2015 - @fabioCollini
1Legacy code
DroidCon Italy – Torino – April 2015 – @fabioCollini 8
Legacy code
Edit and pray
Vs
Cover and modify
Legacy code is code
without unit tests
DroidCon Italy – Torino – April 2015 – @fabioCollini 9
Instrumentation tests
run on a device (real or emulated)
high code coverage
Vs
JVM tests
fast
low code coverage
DroidCon Italy – Torino – April 2015 – @fabioCollini 10
Espresso
public class PostListActivityTest {
//see https://gist.github.com/JakeWharton/1c2f2cadab2ddd97f9fb

@Rule

public ActivityRule<PostListActivity> rule =
new ActivityRule<>(PostListActivity.class);



}
@Test public void showListActivity() {

onView(withText("???"))
.check(matches(isDisplayed()));

}

@Test public void showErrorLayoutOnServerError() {

//???

onView(withId(R.id.error_layout))
.check(matches(isDisplayed()));
}
DroidCon Italy – Torino – April 2015 – @fabioCollini 11
Legacy code dilemma
When we change code,
we should have tests in place.
To put tests in place,
we often have to change code.
Michael Feathers
DroidCon Italy – Torino – April 2015 – @fabioCollini 12
TestablePostListActivity
public class TestablePostListActivity
extends PostListActivity {



public static Observable<List<Post>> result;



@Override
protected Observable<List<Post>> createListObservable() {

return result;

}

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 13
PostListActivityTest
public class PostListActivityTest {


@Rule public ActivityRule<TestablePostListActivity> rule =
new ActivityRule<>(
TestablePostListActivity.class,
false
);



@Test public void showListActivity() {

TestablePostListActivity.result = Observable.just(

createPost(1), createPost(2), createPost(3)

).toList();



rule.launchActivity();



onView(withText("title 1”))
.check(matches(isDisplayed()));

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 14
PostListActivityTest
@Test public void checkErrorLayoutDisplayed() {

TestablePostListActivity.result =
Observable.error(new IOException());



rule.launchActivity();



onView(withId(R.id.error_layout))
.check(matches(isDisplayed()));

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 15
Legacy code
Not the perfect solution
First step to increase coverage
Then modify and refactor
DroidCon Italy - Torino - April 2015 - @fabioCollini
2Dependency Injection
DroidCon Italy – Torino – April 2015 – @fabioCollini 17
PostBatch
public void execute() {

PostResponse postResponse = createService().listPosts();

EmailSender emailSender = new EmailSender();



List<Post> posts = postResponse.getPosts();

for (Post post : posts) {

emailSender.sendEmail(post);

}

}



private static WordPressService createService() {

//...

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 18
PostBatch
WordPressService
EmailSender
Class under test
Collaborator
Collaborator
DroidCon Italy – Torino – April 2015 – @fabioCollini 19
PostBatchTest
public class PostBatchTest {



private PostBatch postBatch = new PostBatch();



@Test

public void testExecute() {

postBatch.execute();

//???

}

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 20
Inversion Of Control
private WordPressService wordPressService;



private EmailSender emailSender;



public PostBatch(WordPressService wordPressService,
EmailSender emailSender) {

this.wordPressService = wordPressService;

this.emailSender = emailSender;

}



public void execute() {

PostResponse postResponse = wordPressService.listPosts();

List<Post> posts = postResponse.getPosts();

for (Post post : posts) {

emailSender.sendEmail(post);

}

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 21
Dependency Injection
public class Main {



public static void main(String[] args) {

new PostBatch(
createService(), new EmailSender()
).execute();

}



private static WordPressService createService() {

//...

}

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 22
WordPressServiceStub
public class WordPressServiceStub
implements WordPressService {



private PostResponse postResponse;



public WordPressServiceStub(PostResponse postResponse) {

this.postResponse = postResponse;

}



@Override public PostResponse listPosts() {

return postResponse;

}

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 23
EmailSenderSpy
public class EmailSenderSpy extends EmailSender {



private int emailCount;



@Override public void sendEmail(Post p) {

emailCount++;

}



public int getEmailCount() {

return emailCount;

}

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 24
PostBatch
WordPressService
EmailSender
Stub
Spy
DroidCon Italy – Torino – April 2015 – @fabioCollini 25
Test doubles
private PostBatch postBatch;



private EmailSenderSpy emailSenderSpy;



private WordPressServiceStub serviceStub;
@Test

public void testExecute() {

postBatch.execute();

assertEquals(3, emailSenderSpy.getEmailCount());

}
@Before public void init() {

emailSenderSpy = new EmailSenderSpy();

serviceStub = new WordPressServiceStub(
new PostResponse(new Post(), new Post(), new Post())
);

postBatch = new PostBatch(serviceStub, emailSenderSpy);

}

DroidCon Italy - Torino - April 2015 - @fabioCollini
3Mockito
DroidCon Italy – Torino – April 2015 – @fabioCollini 27
Mockito
private WordPressService service;

private EmailSender emailSender;

private PostBatch postBatch;

@Before public void init() {

emailSender = Mockito.mock(EmailSender.class);

service = Mockito.mock(WordPressService.class);

postBatch = new PostBatch(service, emailSender);

}
@Test public void testExecute() {

when(service.listPosts()).thenReturn(
new PostResponse(new Post(), new Post(), new Post()));



postBatch.execute();



verify(emailSender, times(3)).sendEmail(any(Post.class));

}
ArrangeActAssert
DroidCon Italy – Torino – April 2015 – @fabioCollini 28
@InjectMocks
@RunWith(MockitoJUnitRunner.class)

public class PostBatchTest {



@Mock WordPressService service;



@Mock EmailSender sender;



@InjectMocks PostBatch postBatch;



@Test

public void testExecute() {

when(service.listPosts()).thenReturn(
new PostResponse(new Post(), new Post(), new Post()));



postBatch.execute();



verify(sender, times(3)).sendEmail(any(Post.class));

}

}
DroidCon Italy - Torino - April 2015 - @fabioCollini
4Dagger
DroidCon Italy – Torino – April 2015 – @fabioCollini 30
Dagger
A fast dependency injector for Android and Java
v1 developed at Square
https://github.com/square/dagger
v2 developed at Google
https://github.com/google/dagger
Configuration using annotations and Java classes
Based on annotation processing (no reflection)
DroidCon Italy – Torino – April 2015 – @fabioCollini 31
Module
@Module

public class MainModule {


@Provides @Singleton EmailSender provideEmailSender() {

return new EmailSender();

}



@Provides @Singleton WordPressService provideService() {

//...

}



@Provides PostBatch providePostsBatch(
WordPressService wordPressService,
EmailSender emailSender) {

return new PostBatch(wordPressService, emailSender);

}

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 32
PostBatch
WordPressService
EmailSender
Component
Main
DroidCon Italy – Torino – April 2015 – @fabioCollini 33
Component
@Singleton

@Component(modules = MainModule.class)

public interface MainComponent {

PostBatch getBatch();

}
public class Main {

public static void main(String[] args) {

MainComponent component = DaggerMainComponent.create();
PostBatch batch = component.getBatch();

batch.execute();

}

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 34
Inject annotation
public class PostBatch {

private WordPressService wordPressService;

private EmailSender emailSender;



@Inject public PostBatch(
WordPressService wordPressService,
EmailSender emailSender) {

this.wordPressService = wordPressService;

this.emailSender = emailSender;

}
}
public class PostBatch {

@Inject WordPressService wordPressService;

@Inject EmailSender emailSender;

@Inject public PostBatch() {

}

}
DroidCon Italy - Torino - April 2015 - @fabioCollini
5Dagger & Android
DroidCon Italy – Torino – April 2015 – @fabioCollini 36
PostListActivity
WordPressService
ShareActivity
ShareExecutor
DroidCon Italy – Torino – April 2015 – @fabioCollini 37
ShareExecutor
public class ShareExecutor {

private Context context;


public ShareExecutor(Context context) {

this.context = context;

}



public void startSendActivity(String title, String body) {

Intent intent = new Intent();

intent.setAction(Intent.ACTION_SEND);

intent.putExtra(Intent.EXTRA_TITLE, title);

intent.putExtra(Intent.EXTRA_TEXT, body);

intent.setType("text/plain");

Intent chooserIntent = Intent.createChooser(intent,
context.getResources().getText(R.string.share));

chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(chooserIntent);
}

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 38
ApplicationModule
@Module public class ApplicationModule {

private Application application;

public ApplicationModule(Application application) {

this.application = application;

}


@Provides @Singleton WordPressService providesService() {

//...

}

@Provides @Singleton ShareExecutor shareExecutor() {

return new ShareExecutor(application);

}

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 39
ApplicationComponent
@Singleton
@Component(modules = ApplicationModule.class)

public interface ApplicationComponent {


void inject(PostListActivity activity);

void inject(ShareActivity activity);
}
DroidCon Italy – Torino – April 2015 – @fabioCollini 40
Component
PostListActivity
Application
ShareActivity
ShareExecutorWordPressService
DroidCon Italy – Torino – April 2015 – @fabioCollini 41
Application
public class CnjApplication extends Application {



private ApplicationComponent component;



@Override public void onCreate() {

super.onCreate();

component = DaggerApplicationComponent.builder()

.applicationModule(new ApplicationModule(this))

.build();

}



public ApplicationComponent getComponent() {

return component;

}



public void setComponent(ApplicationComponent c) {

this.component = c;

}

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 42
PostListActivity
public class PostListActivity extends ActionBarActivity {

//...

@Inject WordPressService wordPressService;



@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

CnjApplication app =
(CnjApplication) getApplicationContext();
ApplicationComponent component = app.getComponent();

component.inject(this);



//...

}

//...

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 43
Component
PostListActivity
Application
ShareExecutorWordPressService
TestComponent
MockMock
Test
DroidCon Italy – Torino – April 2015 – @fabioCollini 44
TestModule
@Module

public class TestModule {

@Provides @Singleton
ShareExecutor provideShareExecutor() {

return Mockito.mock(ShareExecutor.class);

}

@Provides @Singleton
WordPressService providesWordPressService() {

return Mockito.mock(WordPressService.class);

}

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 45
TestComponent
@Singleton

@Component(modules = TestModule.class)

public interface TestComponent
extends ApplicationComponent {

void inject(PostListActivityTest test);



void inject(ShareActivityTest test);

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 46
PostListActivityTest
public class PostListActivityTest {



@Inject WordPressService wordPressService;



@Rule public ActivityRule<PostListActivity> rule =
new ActivityRule<>(PostListActivity.class, false);

@Before public void setUp() {

TestComponent component =
DaggerTestComponent.create();

CnjApplication application =
(CnjApplication) rule.getApplication();

application.setComponent(component);

component.inject(this);

}
//...
DroidCon Italy – Torino – April 2015 – @fabioCollini 47
PostListActivityTest
@Test
public void showListActivity() {

when(wordPressService.listPosts()).thenReturn(
Observable.just(new PostResponse(createPost(1),
createPost(2), createPost(3))));



rule.launchActivity();



onView(withText("title 1”))
.check(matches(isDisplayed()));

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 48
PostListActivityTest


@Test
public void showErrorLayoutOnServerError() {

when(wordPressService.listPosts()).thenReturn(
Observable.error(new IOException("error!")));



rule.launchActivity();



onView(withId(R.id.error_layout))
.check(matches(isDisplayed()));

}
DroidCon Italy - Torino - April 2015 - @fabioCollini
6Model View Presenter
DroidCon Italy – Torino – April 2015 – @fabioCollini 50
Model View Presenter
View
Presenter
Model
DroidCon Italy – Torino – April 2015 – @fabioCollini 51
View Presenter RetrofitService
onClick
update
update(model)
Model
View Presenter RetrofitServiceModel
request
response
DroidCon Italy – Torino – April 2015 – @fabioCollini 52
View Presenter MockService
perform(click())
update
update(model)
Model
request
response
EspressoTest
View Presenter MockServiceModelEspressoTest
onView
verify
when().thenReturn()
onClick
DroidCon Italy – Torino – April 2015 – @fabioCollini 53
MockView Presenter MockService
onClick
update
update(model)
Model
request
response
JVM Test
MockView Presenter MockServiceModelJVM Test
verify
verify
assert
when().thenReturn()
DroidCon Italy – Torino – April 2015 – @fabioCollini 54
View Presenter
init
update(model)
ModelEspressoTest
View Presenter ModelEspressoTest
onView
create
DroidCon Italy – Torino – April 2015 – @fabioCollini 55
Android Model View Presenter
Activity (or Fragment) is the View
All the business logic is in the Presenter
Presenter is managed using Dagger
Model is the Activity (or Fragment) state
Presenter is retained on configuration change
DroidCon Italy – Torino – April 2015 – @fabioCollini 56
PostListPresenter JVM Test
@RunWith(MockitoJUnitRunner.class)

public class PostListPresenterTest {



@Mock WordPressService wordPressService;



@Mock PostListActivity view;



private PostListPresenter postListPresenter;



@Before public void setUp() {
SchedulerManager schedulerManager =
new SchedulerManager(
Schedulers.immediate(), Schedulers.immediate());


postListPresenter = new PostListPresenter(
wordPressService, schedulerManager
);

}

//...
DroidCon Italy – Torino – April 2015 – @fabioCollini 57
PostListPresenter JVM Test
@Test

public void loadingPosts() {
when(wordPressService.listPosts()).thenReturn(
Observable.just(new PostResponse(new Post(),
new Post(), new Post())));



PostListModel model = new PostListModel();

postListPresenter.setModel(model);

postListPresenter.resume(view);



assertEquals(3, model.getItems().size());

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 58
PostListPresenter JVM Test
@Test

public void clickOnItem() {

PostListModel model = new PostListModel();

model.setItems(Arrays.asList(
createPost(1), createPost(2), createPost(3)));

postListPresenter.setModel(model);

postListPresenter.resume(view);



postListPresenter.onItemClick(1);



verify(view).startShareActivity(
eq("title 2"), eq("name 2 last name 2nexcerpt 2"));

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 59
SharePresenter JVM Test
@RunWith(MockitoJUnitRunner.class)

public class SharePresenterTest {



@Mock ShareExecutor shareExecutor;



@Mock ShareActivity view;



@InjectMocks SharePresenter sharePresenter;



@Test public void testValidationOk() {
ShareModel model = new ShareModel();

sharePresenter.init(view, model);



sharePresenter.share("title", "body");



verify(shareExecutor)
.startSendActivity(eq("title"), eq("body"));

}

}
DroidCon Italy – Torino – April 2015 – @fabioCollini 60
TL;DR
Dagger
Mockito
Espresso UI tests
JVM Presenter tests
DroidCon Italy – Torino – April 2015 – @fabioCollini 61
Thanks for your attention!
Questions?
github.com/fabioCollini/
TestableAndroidAppsDroidCon15
github.com/google/dagger
mockito.org
cosenonjaviste.it/libri

Testable Android Apps DroidCon Italy 2015

  • 1.
  • 2.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 2 Fabio Collini @fabioCollini linkedin.com/in/fabiocollini Folder Organizer cosenonjaviste.it nana bianca Freapp instal.com Rain tomorrow?
  • 3.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 3 Quick survey Do you write automated tests?
  • 4.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 4 Return of Investment - ROI Net profit Investment
  • 5.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 5 Agenda 1. Legacy code 2. Dependency Injection 3. Mockito 4. Dagger 5. Dagger & Android 6. Model View Presenter
  • 6.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 6 TestableAndroidAppsDroidCon15 https://github.com/fabioCollini/ TestableAndroidAppsDroidCon15
  • 7.
    DroidCon Italy -Torino - April 2015 - @fabioCollini 1Legacy code
  • 8.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 8 Legacy code Edit and pray Vs Cover and modify Legacy code is code without unit tests
  • 9.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 9 Instrumentation tests run on a device (real or emulated) high code coverage Vs JVM tests fast low code coverage
  • 10.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 10 Espresso public class PostListActivityTest { //see https://gist.github.com/JakeWharton/1c2f2cadab2ddd97f9fb
 @Rule
 public ActivityRule<PostListActivity> rule = new ActivityRule<>(PostListActivity.class);
 
 } @Test public void showListActivity() {
 onView(withText("???")) .check(matches(isDisplayed()));
 }
 @Test public void showErrorLayoutOnServerError() {
 //???
 onView(withId(R.id.error_layout)) .check(matches(isDisplayed())); }
  • 11.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 11 Legacy code dilemma When we change code, we should have tests in place. To put tests in place, we often have to change code. Michael Feathers
  • 12.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 12 TestablePostListActivity public class TestablePostListActivity extends PostListActivity {
 
 public static Observable<List<Post>> result;
 
 @Override protected Observable<List<Post>> createListObservable() {
 return result;
 }
 }
  • 13.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 13 PostListActivityTest public class PostListActivityTest { 
 @Rule public ActivityRule<TestablePostListActivity> rule = new ActivityRule<>( TestablePostListActivity.class, false );
 
 @Test public void showListActivity() {
 TestablePostListActivity.result = Observable.just(
 createPost(1), createPost(2), createPost(3)
 ).toList();
 
 rule.launchActivity();
 
 onView(withText("title 1”)) .check(matches(isDisplayed()));
 }
  • 14.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 14 PostListActivityTest @Test public void checkErrorLayoutDisplayed() {
 TestablePostListActivity.result = Observable.error(new IOException());
 
 rule.launchActivity();
 
 onView(withId(R.id.error_layout)) .check(matches(isDisplayed()));
 }
  • 15.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 15 Legacy code Not the perfect solution First step to increase coverage Then modify and refactor
  • 16.
    DroidCon Italy -Torino - April 2015 - @fabioCollini 2Dependency Injection
  • 17.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 17 PostBatch public void execute() {
 PostResponse postResponse = createService().listPosts();
 EmailSender emailSender = new EmailSender();
 
 List<Post> posts = postResponse.getPosts();
 for (Post post : posts) {
 emailSender.sendEmail(post);
 }
 }
 
 private static WordPressService createService() {
 //...
 }
  • 18.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 18 PostBatch WordPressService EmailSender Class under test Collaborator Collaborator
  • 19.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 19 PostBatchTest public class PostBatchTest {
 
 private PostBatch postBatch = new PostBatch();
 
 @Test
 public void testExecute() {
 postBatch.execute();
 //???
 }
 }
  • 20.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 20 Inversion Of Control private WordPressService wordPressService;
 
 private EmailSender emailSender;
 
 public PostBatch(WordPressService wordPressService, EmailSender emailSender) {
 this.wordPressService = wordPressService;
 this.emailSender = emailSender;
 }
 
 public void execute() {
 PostResponse postResponse = wordPressService.listPosts();
 List<Post> posts = postResponse.getPosts();
 for (Post post : posts) {
 emailSender.sendEmail(post);
 }
 }
  • 21.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 21 Dependency Injection public class Main {
 
 public static void main(String[] args) {
 new PostBatch( createService(), new EmailSender() ).execute();
 }
 
 private static WordPressService createService() {
 //...
 }
 }
  • 22.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 22 WordPressServiceStub public class WordPressServiceStub implements WordPressService {
 
 private PostResponse postResponse;
 
 public WordPressServiceStub(PostResponse postResponse) {
 this.postResponse = postResponse;
 }
 
 @Override public PostResponse listPosts() {
 return postResponse;
 }
 }
  • 23.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 23 EmailSenderSpy public class EmailSenderSpy extends EmailSender {
 
 private int emailCount;
 
 @Override public void sendEmail(Post p) {
 emailCount++;
 }
 
 public int getEmailCount() {
 return emailCount;
 }
 }
  • 24.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 24 PostBatch WordPressService EmailSender Stub Spy
  • 25.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 25 Test doubles private PostBatch postBatch;
 
 private EmailSenderSpy emailSenderSpy;
 
 private WordPressServiceStub serviceStub; @Test
 public void testExecute() {
 postBatch.execute();
 assertEquals(3, emailSenderSpy.getEmailCount());
 } @Before public void init() {
 emailSenderSpy = new EmailSenderSpy();
 serviceStub = new WordPressServiceStub( new PostResponse(new Post(), new Post(), new Post()) );
 postBatch = new PostBatch(serviceStub, emailSenderSpy);
 }

  • 26.
    DroidCon Italy -Torino - April 2015 - @fabioCollini 3Mockito
  • 27.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 27 Mockito private WordPressService service;
 private EmailSender emailSender;
 private PostBatch postBatch;
 @Before public void init() {
 emailSender = Mockito.mock(EmailSender.class);
 service = Mockito.mock(WordPressService.class);
 postBatch = new PostBatch(service, emailSender);
 } @Test public void testExecute() {
 when(service.listPosts()).thenReturn( new PostResponse(new Post(), new Post(), new Post()));
 
 postBatch.execute();
 
 verify(emailSender, times(3)).sendEmail(any(Post.class));
 } ArrangeActAssert
  • 28.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 28 @InjectMocks @RunWith(MockitoJUnitRunner.class)
 public class PostBatchTest {
 
 @Mock WordPressService service;
 
 @Mock EmailSender sender;
 
 @InjectMocks PostBatch postBatch;
 
 @Test
 public void testExecute() {
 when(service.listPosts()).thenReturn( new PostResponse(new Post(), new Post(), new Post()));
 
 postBatch.execute();
 
 verify(sender, times(3)).sendEmail(any(Post.class));
 }
 }
  • 29.
    DroidCon Italy -Torino - April 2015 - @fabioCollini 4Dagger
  • 30.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 30 Dagger A fast dependency injector for Android and Java v1 developed at Square https://github.com/square/dagger v2 developed at Google https://github.com/google/dagger Configuration using annotations and Java classes Based on annotation processing (no reflection)
  • 31.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 31 Module @Module
 public class MainModule { 
 @Provides @Singleton EmailSender provideEmailSender() {
 return new EmailSender();
 }
 
 @Provides @Singleton WordPressService provideService() {
 //...
 }
 
 @Provides PostBatch providePostsBatch( WordPressService wordPressService, EmailSender emailSender) {
 return new PostBatch(wordPressService, emailSender);
 }
 }
  • 32.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 32 PostBatch WordPressService EmailSender Component Main
  • 33.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 33 Component @Singleton
 @Component(modules = MainModule.class)
 public interface MainComponent {
 PostBatch getBatch();
 } public class Main {
 public static void main(String[] args) {
 MainComponent component = DaggerMainComponent.create(); PostBatch batch = component.getBatch();
 batch.execute();
 }
 }
  • 34.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 34 Inject annotation public class PostBatch {
 private WordPressService wordPressService;
 private EmailSender emailSender;
 
 @Inject public PostBatch( WordPressService wordPressService, EmailSender emailSender) {
 this.wordPressService = wordPressService;
 this.emailSender = emailSender;
 } } public class PostBatch {
 @Inject WordPressService wordPressService;
 @Inject EmailSender emailSender;
 @Inject public PostBatch() {
 }
 }
  • 35.
    DroidCon Italy -Torino - April 2015 - @fabioCollini 5Dagger & Android
  • 36.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 36 PostListActivity WordPressService ShareActivity ShareExecutor
  • 37.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 37 ShareExecutor public class ShareExecutor {
 private Context context; 
 public ShareExecutor(Context context) {
 this.context = context;
 }
 
 public void startSendActivity(String title, String body) {
 Intent intent = new Intent();
 intent.setAction(Intent.ACTION_SEND);
 intent.putExtra(Intent.EXTRA_TITLE, title);
 intent.putExtra(Intent.EXTRA_TEXT, body);
 intent.setType("text/plain");
 Intent chooserIntent = Intent.createChooser(intent, context.getResources().getText(R.string.share));
 chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(chooserIntent); }
 }
  • 38.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 38 ApplicationModule @Module public class ApplicationModule {
 private Application application;
 public ApplicationModule(Application application) {
 this.application = application;
 } 
 @Provides @Singleton WordPressService providesService() {
 //...
 }
 @Provides @Singleton ShareExecutor shareExecutor() {
 return new ShareExecutor(application);
 }
 }
  • 39.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 39 ApplicationComponent @Singleton @Component(modules = ApplicationModule.class)
 public interface ApplicationComponent { 
 void inject(PostListActivity activity);
 void inject(ShareActivity activity); }
  • 40.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 40 Component PostListActivity Application ShareActivity ShareExecutorWordPressService
  • 41.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 41 Application public class CnjApplication extends Application {
 
 private ApplicationComponent component;
 
 @Override public void onCreate() {
 super.onCreate();
 component = DaggerApplicationComponent.builder()
 .applicationModule(new ApplicationModule(this))
 .build();
 }
 
 public ApplicationComponent getComponent() {
 return component;
 }
 
 public void setComponent(ApplicationComponent c) {
 this.component = c;
 }
 }
  • 42.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 42 PostListActivity public class PostListActivity extends ActionBarActivity {
 //...
 @Inject WordPressService wordPressService;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 CnjApplication app = (CnjApplication) getApplicationContext(); ApplicationComponent component = app.getComponent();
 component.inject(this);
 
 //...
 }
 //...
 }
  • 43.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 43 Component PostListActivity Application ShareExecutorWordPressService TestComponent MockMock Test
  • 44.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 44 TestModule @Module
 public class TestModule {
 @Provides @Singleton ShareExecutor provideShareExecutor() {
 return Mockito.mock(ShareExecutor.class);
 }
 @Provides @Singleton WordPressService providesWordPressService() {
 return Mockito.mock(WordPressService.class);
 }
 }
  • 45.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 45 TestComponent @Singleton
 @Component(modules = TestModule.class)
 public interface TestComponent extends ApplicationComponent {
 void inject(PostListActivityTest test);
 
 void inject(ShareActivityTest test);
 }
  • 46.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 46 PostListActivityTest public class PostListActivityTest {
 
 @Inject WordPressService wordPressService;
 
 @Rule public ActivityRule<PostListActivity> rule = new ActivityRule<>(PostListActivity.class, false);
 @Before public void setUp() {
 TestComponent component = DaggerTestComponent.create();
 CnjApplication application = (CnjApplication) rule.getApplication();
 application.setComponent(component);
 component.inject(this);
 } //...
  • 47.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 47 PostListActivityTest @Test public void showListActivity() {
 when(wordPressService.listPosts()).thenReturn( Observable.just(new PostResponse(createPost(1), createPost(2), createPost(3))));
 
 rule.launchActivity();
 
 onView(withText("title 1”)) .check(matches(isDisplayed()));
 }
  • 48.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 48 PostListActivityTest 
 @Test public void showErrorLayoutOnServerError() {
 when(wordPressService.listPosts()).thenReturn( Observable.error(new IOException("error!")));
 
 rule.launchActivity();
 
 onView(withId(R.id.error_layout)) .check(matches(isDisplayed()));
 }
  • 49.
    DroidCon Italy -Torino - April 2015 - @fabioCollini 6Model View Presenter
  • 50.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 50 Model View Presenter View Presenter Model
  • 51.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 51 View Presenter RetrofitService onClick update update(model) Model View Presenter RetrofitServiceModel request response
  • 52.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 52 View Presenter MockService perform(click()) update update(model) Model request response EspressoTest View Presenter MockServiceModelEspressoTest onView verify when().thenReturn() onClick
  • 53.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 53 MockView Presenter MockService onClick update update(model) Model request response JVM Test MockView Presenter MockServiceModelJVM Test verify verify assert when().thenReturn()
  • 54.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 54 View Presenter init update(model) ModelEspressoTest View Presenter ModelEspressoTest onView create
  • 55.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 55 Android Model View Presenter Activity (or Fragment) is the View All the business logic is in the Presenter Presenter is managed using Dagger Model is the Activity (or Fragment) state Presenter is retained on configuration change
  • 56.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 56 PostListPresenter JVM Test @RunWith(MockitoJUnitRunner.class)
 public class PostListPresenterTest {
 
 @Mock WordPressService wordPressService;
 
 @Mock PostListActivity view;
 
 private PostListPresenter postListPresenter;
 
 @Before public void setUp() { SchedulerManager schedulerManager = new SchedulerManager( Schedulers.immediate(), Schedulers.immediate()); 
 postListPresenter = new PostListPresenter( wordPressService, schedulerManager );
 }
 //...
  • 57.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 57 PostListPresenter JVM Test @Test
 public void loadingPosts() { when(wordPressService.listPosts()).thenReturn( Observable.just(new PostResponse(new Post(), new Post(), new Post())));
 
 PostListModel model = new PostListModel();
 postListPresenter.setModel(model);
 postListPresenter.resume(view);
 
 assertEquals(3, model.getItems().size());
 }
  • 58.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 58 PostListPresenter JVM Test @Test
 public void clickOnItem() {
 PostListModel model = new PostListModel();
 model.setItems(Arrays.asList( createPost(1), createPost(2), createPost(3)));
 postListPresenter.setModel(model);
 postListPresenter.resume(view);
 
 postListPresenter.onItemClick(1);
 
 verify(view).startShareActivity( eq("title 2"), eq("name 2 last name 2nexcerpt 2"));
 }
  • 59.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 59 SharePresenter JVM Test @RunWith(MockitoJUnitRunner.class)
 public class SharePresenterTest {
 
 @Mock ShareExecutor shareExecutor;
 
 @Mock ShareActivity view;
 
 @InjectMocks SharePresenter sharePresenter;
 
 @Test public void testValidationOk() { ShareModel model = new ShareModel();
 sharePresenter.init(view, model);
 
 sharePresenter.share("title", "body");
 
 verify(shareExecutor) .startSendActivity(eq("title"), eq("body"));
 }
 }
  • 60.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 60 TL;DR Dagger Mockito Espresso UI tests JVM Presenter tests
  • 61.
    DroidCon Italy –Torino – April 2015 – @fabioCollini 61 Thanks for your attention! Questions? github.com/fabioCollini/ TestableAndroidAppsDroidCon15 github.com/google/dagger mockito.org cosenonjaviste.it/libri