SlideShare a Scribd company logo
1 of 58
Download to read offline
Testable Android Apps
using data binding and MVVM
Fabio Collini
GDG DevFest – Milano – October 2015 – @fabioCollini 2
Ego slide
@fabioCollini
linkedin.com/in/fabiocollini
Folder Organizer
cosenonjaviste.it
nana bianca
Freapp
instal.com
Rain tomorrow?
GDG DevFest – Milano – October 2015 – @fabioCollini 3
Agenda
1. ROI and Legacy code
2. Model View ViewModel
3. JVM Unit tests
4. Mockito
5. Espresso
GDG DevFest - Milano - October 2015 - @fabioCollini 4
1ROI and legacy code
GDG DevFest – Milano – October 2015 – @fabioCollini 5
Quick survey
Do you write automated tests?
GDG DevFest – Milano – October 2015 – @fabioCollini 6
GDG DevFest – Milano – October 2015 – @fabioCollini 7
Return of Investment - ROI
Net profit
Investment
GDG DevFest – Milano – October 2015 – @fabioCollini 8
Legacy code
Edit and pray
Vs
Cover and modify
Legacy code is code
without unit tests
GDG DevFest – Milano – October 2015 – @fabioCollini 9
Test After Development
Write the feature implementation
Do some manual testing
Try to write automatic tests
Modify the initial implementation to test it
“Standard” Android code is not testable :(
GDG DevFest – Milano – October 2015 – @fabioCollini 10
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
GDG DevFest - Milano - October 2015 - @fabioCollini 11
2Model View ViewModel
GDG DevFest – Milano – October 2015 – @fabioCollini 12
Testable code
Data binding and MVVM
GDG DevFest – Milano – October 2015 – @fabioCollini 13
Model View ViewModel
View
ViewModel
Model
DataBinding
GDG DevFest – Milano – October 2015 – @fabioCollini 14
Android Model View ViewModel
View
ViewModel
Model
DataBinding
Retained on
configuration change
Saved in Activity or
Fragment state
Activity or Fragment
GDG DevFest – Milano – October 2015 – @fabioCollini 15
mv2m
https://github.com/fabioCollini/mv2m
GDG DevFest – Milano – October 2015 – @fabioCollini 16
NoteActivity
NoteViewModel
NoteModel
note_detail.xml
NoteDetailBinding
DataBinding
GDG DevFest – Milano – October 2015 – @fabioCollini 17
View ViewModel RetrofitService
onClick
update
binding
Model
View ViewModel RetrofitServiceModel
request
response
binding
GDG DevFest – Milano – October 2015 – @fabioCollini 18
NoteModel
Saved on Activity state
public class NoteModel implements Parcelable {



private long noteId;



private ObservableBoolean error = new ObservableBoolean();



private ObservableString title = new ObservableString();



private ObservableString text = new ObservableString();



private ObservableInt titleError = new ObservableInt();



private ObservableInt textError = new ObservableInt();



//...

}
GDG DevFest – Milano – October 2015 – @fabioCollini 19
NoteActivity
public class NoteActivity extends
ViewModelActivity<NoteViewModel> {



@Override public NoteViewModel createViewModel() {

return new NoteViewModel(/* .. */);

}



@Override protected void onCreate(Bundle state) {

super.onCreate(state);

NoteDetailBinding binding =
DataBindingUtil.setContentView(this,
R.layout.note_detail);

binding.setViewModel(viewModel);

}

}
GDG DevFest – Milano – October 2015 – @fabioCollini 20
note_detail.xml
<?xml version="1.0" encoding="utf-8"?>

<layout
xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res-auto">



<data>

<variable

name="viewModel"

type="it.cosenonjaviste.core.NoteViewModel"/>

</data>



<FrameLayout

android:layout_width="match_parent"

android:layout_height="match_parent">



<!-- ... -->



</FrameLayout>

</layout>
GDG DevFest – Milano – October 2015 – @fabioCollini 21
note_detail.xml
<LinearLayout app:visible="@{viewModel.loading}">

<ProgressBar />

<TextView />

</LinearLayout>



<LinearLayout app:visible=“@{viewModel.model.error}">


<TextView android:text="@string/error_loading_note"/>



<Button android:text=“@string/retry"/>

</LinearLayout>



<ScrollView app:visible="@{!viewModel.loading &amp;&amp;
!viewModel.model.error}">

<!-- ... -->

</ScrollView>
GDG DevFest – Milano – October 2015 – @fabioCollini 22
note_detail.xml
<LinearLayout>

<android.support.design.widget.TextInputLayout

app:error=“@{viewModel.model.titleError}">


<EditText app:binding=“@{viewModel.model.title}" />


</android.support.design.widget.TextInputLayout>

<!-- ... -->
<RelativeLayout>


<Button
android:enabled="@{!viewModel.sending}"

app:onClick="@{viewModel.save}"/>

<ProgressBar app:visible=“@{viewModel.sending}" />

</RelativeLayout>

</LinearLayout>
GDG DevFest – Milano – October 2015 – @fabioCollini 23
app:binding
@BindingAdapter({"app:binding"})

public static void bindEditText(EditText view,
final ObservableString observableString) {

if (view.getTag(R.id.binded) == null) {

view.setTag(R.id.binded, true);

view.addTextChangedListener(new TextWatcherAdapter() {

@Override public void onTextChanged(
CharSequence s, int st, int b, int c) {

observableString.set(s.toString());

}

});

}

String newValue = observableString.get();

if (!view.getText().toString().equals(newValue)) {

view.setText(newValue);

}

}
GDG DevFest – Milano – October 2015 – @fabioCollini 24
app:visible app:onClick
@BindingAdapter({"app:visible"})

public static void bindVisible(View view, boolean b) {

view.setVisibility(b ? View.VISIBLE : View.INVISIBLE);

}



@BindingAdapter({"app:onClick"})

public static void bindOnClick(View view,
final Runnable listener) {

view.setOnClickListener(new View.OnClickListener() {

@Override public void onClick(View v) {

listener.run();

}

});

}
GDG DevFest – Milano – October 2015 – @fabioCollini 25
NoteViewModel
public class NoteViewModel extends ViewModel<NoteModel> {



//...



@Override public NoteModel createDefaultModel() {

return new NoteModel();

}



@Override public void resume() {

if (!getModel().isLoaded()) {

reloadData();

}

}



public void reloadData() {

}



//...

}
GDG DevFest - Milano - October 2015 - @fabioCollini 26
3JVM Unit tests
GDG DevFest – Milano – October 2015 – @fabioCollini 27
Instrumentation tests
run on a device (real or emulated)
high code coverage
Vs
JVM tests
fast
low code coverage
GDG DevFest – Milano – October 2015 – @fabioCollini
JVM Test
28
NoteActivity
NoteViewModel
NoteModel
note_detail.xml
NoteDetailBinding
DataBinding
GDG DevFest – Milano – October 2015 – @fabioCollini 29
NoteViewModel.reloadData
public class NoteViewModel extends ViewModel<NoteModel> {



//...

@Override public void resume() {

if (!getModel().isLoaded()) {

reloadData();

}

}



public void reloadData() {

try {

Note note = NoteLoader.singleton().load();

getModel().update(note);

} catch (Exception e) {

getModel().getError().set(true);

}

}

//...

}
GDG DevFest – Milano – October 2015 – @fabioCollini 30
First test
AssertJ
@Test

public void testLoadData() {

NoteViewModel viewModel = new NoteViewModel();



NoteModel model = viewModel.initAndResume();



assertThat(model.getTitle().get()).isEqualTo("???");

assertThat(model.getText().get()).isEqualTo("???");

assertThat(model.getError().get()).isFalse();

}
GDG DevFest – Milano – October 2015 – @fabioCollini 31
NoteLoader.singleton
public class NoteViewModel extends ViewModel<NoteModel> {



//...

@Override public void resume() {

if (!getModel().isLoaded()) {

reloadData();

}

}



public void reloadData() {

try {

Note note = NoteLoader.singleton().load();

getModel().update(note);

} catch (Exception e) {

getModel().getError().set(true);

}

}

//...

}
GDG DevFest – Milano – October 2015 – @fabioCollini 32
Dependency Injection
public class NoteViewModel
extends ViewModel<NoteModel, NoteView> {
private NoteLoader noteLoader;

public NoteViewModel(NoteLoader noteLoader) {

this.noteLoader = noteLoader;

}


public void reloadData() {

try {

Note note = noteLoader.load();

getModel().update(note);

} catch (Exception e) {

getModel().getError().set(true);

}

}
//...
}
GDG DevFest – Milano – October 2015 – @fabioCollini 33
NoteLoaderStub
public class NoteLoaderStub implements NoteLoader {


private Note note;



public NoteLoaderStub(Note note) {

this.note = note;

}



@Override public Note load() {

return note;

}

}
GDG DevFest – Milano – October 2015 – @fabioCollini 34
Test with stub
@Test

public void testLoadData() {

NoteLoaderStub stub =
new NoteLoaderStub(new Note(1, "a", "b"));


NoteViewModel viewModel = new NoteViewModel(stub);



NoteModel model = viewModel.initAndResume();



assertThat(model.getTitle().get()).isEqualTo("a");

assertThat(model.getText().get()).isEqualTo("b");

assertThat(model.getError().get()).isFalse();

}
GDG DevFest – Milano – October 2015 – @fabioCollini
public void save() {

NoteModel model = getModel();

boolean titleValid = checkMandatory(
model.getTitle(), model.getTitleError());

boolean textValid = checkMandatory(
model.getText(), model.getTextError());

if (titleValid && textValid) {

try {

noteSaver.save(
model.getNoteId(),
model.getTitle().get(),
model.getText().get());
messageManager.showMessage(R.string.note_saved);

} catch (RetrofitError e) {

messageManager.showMessage(
R.string.error_saving_note);

}

}

}
35
save method
Dependency Injection
Dependency Injection
GDG DevFest – Milano – October 2015 – @fabioCollini 36
SnackbarMessageManager
public class SnackbarMessageManager implements MessageManager {

private Activity activity;



@Override public void showMessage(int message) {

if (activity != null) {

Snackbar.make(
activity.findViewById(android.R.id.content),
message,
Snackbar.LENGTH_LONG
).show();

}

}



@Override public void setActivity(Activity activity) {

this.activity = activity;

}

}
GDG DevFest – Milano – October 2015 – @fabioCollini 37
MessageManagerSpy
public class MessageManagerSpy implements MessageManager {

public int message;



@Override public void showMessage(int message) {

this.message = message;

}



@Override public void setActivity(Activity activity) {

}

}
GDG DevFest – Milano – October 2015 – @fabioCollini 38
NoteSaverSpy
public class NoteSaverSpy implements NoteSaver {



public long id;

public String title;

public String text;



@Override public Response save(
long id, String title, String text) {

this.id = id;

this.title = title;

this.text = text;

return null;

}

}
GDG DevFest – Milano – October 2015 – @fabioCollini 39
Test with spy
@Test

public void testSaveData() {

NoteLoaderStub stub =
new NoteLoaderStub(new Note(1, "a", "b"));
NoteSaverSpy saverSpy = new NoteSaverSpy();

MessageManagerSpy messageSpy = new MessageManagerSpy();
NoteViewModel viewModel = new NoteViewModel(
stub, saverSpy, messageSpy);


NoteModel model = viewModel.initAndResume();

model.getTitle().set("newTitle");

model.getText().set("newText");

viewModel.save();



assertThat(saverSpy.id).isEqualTo(1L);

assertThat(saverSpy.title).isEqualTo("newTitle");

assertThat(saverSpy.text).isEqualTo("newText");
assertThat(messageSpy.message)
.isEqualTo(R.string.note_saved);

}
GDG DevFest - Milano - October 2015 - @fabioCollini 40
4Mockito
GDG DevFest – Milano – October 2015 – @fabioCollini 41
Mockito
@Test

public void testLoadData() {

NoteLoader noteLoader =

Mockito.mock(NoteLoader.class);

NoteSaver noteSaver = 

Mockito.mock(NoteSaver.class);
MessageManager messageManager =
Mockito.mock(MessageManager.class);


NoteViewModel viewModel = new NoteViewModel(

noteLoader, noteSaver, messageManager);



when(noteLoader.load())

.thenReturn(new Note(123, "title", "text"));

NoteModel model = viewModel.initAndResume();



assertThat(model.getTitle().get()).isEqualTo("title");

assertThat(model.getText().get()).isEqualTo("text");

}
GDG DevFest – Milano – October 2015 – @fabioCollini
MockLoader
MockLoaderNoteLoader
NoteLoader
42
ViewModel
initAndResume
update
Model
request
response
JVM Test
ViewModel ModelJVM Test
assert
when().thenReturn()
GDG DevFest – Milano – October 2015 – @fabioCollini 43
Mockito
@Test

public void testSaveData() {

//...


NoteModel model = viewModel.initAndResume();



model.getTitle().set("newTitle");

model.getText().set("newText");

viewModel.save();



verify(noteSaver)
.save(eq(123L), eq("newTitle"), eq("newText"));



verify(messageManager)
.showMessage(eq(R.string.note_saved));

}

GDG DevFest – Milano – October 2015 – @fabioCollini
MockMessage
Manager
Message
Manager
MockMessage
Manager
Message
Manager
44
ViewModel MockSaver
save
showMessage
Model
request
response
JVM Test
ViewModel MockSaverModelJVM Test
verify
verify
NoteSaver
NoteSaver
GDG DevFest – Milano – October 2015 – @fabioCollini 45
SetUp method
public class NoteViewModelTest {

private NoteLoader noteLoader;

private NoteSaver noteSaver;

private MessageManager messageManager;
private NoteViewModel viewModel;



@Before public void setUp() {

noteLoader = Mockito.mock(NoteLoader.class);

noteSaver = Mockito.mock(NoteSaver.class);

messageManager = Mockito.mock(MessageManager.class);


viewModel = new NoteViewModel(
noteLoader, noteSaver, messageManager);
when(noteLoader.load())

.thenReturn(new Note(123, "title", "text"));

}

//...

}
GDG DevFest – Milano – October 2015 – @fabioCollini 46
@Mock and @InjectMocks
@RunWith(MockitoJUnitRunner.class)

public class NoteViewModelTest {



@Mock NoteLoader noteLoader;



@Mock NoteSaver noteSaver;



@Mock MessageManager messageManager;



@InjectMocks NoteViewModel viewModel;



@Before public void setUp() throws Exception {

when(noteLoader.load())

.thenReturn(new Note(123, "title", "text"));

}


//...

}
GDG DevFest – Milano – October 2015 – @fabioCollini 47
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)
GDG DevFest – Milano – October 2015 – @fabioCollini 48
Background executor
if (titleValid && textValid) {

sending.set(true);

backgroundExecutor.execute(new Runnable() {

@Override public void run() {

try {

noteSaver.save(getModel().getNoteId(),
getModel().getTitle().get(),
getModel().getText().get());

hideSendProgressAndShowMessage(
R.string.note_saved);

} catch (RetrofitError e) {

hideSendProgressAndShowMessage(
R.string.error_saving_note);

}

}

});

}
GDG DevFest – Milano – October 2015 – @fabioCollini 49
Ui Executor
private void hideSendProgressAndShowMessage(final int msg) {

uiExecutor.execute(new Runnable() {

@Override public void run() {

messageManager.showMessage(msg);

sending.set(false);

}

});

}

GDG DevFest – Milano – October 2015 – @fabioCollini 50
Test using single thread
@RunWith(MockitoJUnitRunner.class)

public class NoteViewModelTest {



@Mock NoteView view;



@Mock NoteLoader noteLoader;



@Mock NoteSaver noteSaver;



@Spy Executor executor = new Executor() {

@Override public void execute(Runnable command) {

command.run();

}

};



@InjectMocks NoteViewModel viewModel;



//...

}
GDG DevFest - Milano - October 2015 - @fabioCollini 51
5Espresso
GDG DevFest – Milano – October 2015 – @fabioCollini 52
NoteLoader
public class NoteLoader {

private static NoteLoader instance;



public static NoteLoader singleton() {

if (instance == null) {

instance = new NoteLoader();

}

return instance;

}



private NoteLoader() {

}



@VisibleForTesting

public static void setInstance(NoteLoader instance) {

NoteLoader.instance = instance;

}



//...

}
GDG DevFest – Milano – October 2015 – @fabioCollini
public class NoteActivityTest {


@Rule public ActivityTestRule<NoteActivity> rule =
new ActivityTestRule<>(NoteActivity.class, false, false);



private NoteLoader noteLoader;


@Before public void setUp() throws Exception {

noteLoader = Mockito.mock(NoteLoader.class);

NoteLoader.setInstance(noteLoader);

}
//...

}
53
NoteActivityTest
GDG DevFest – Milano – October 2015 – @fabioCollini 54
Reload test
@Test

public void testReloadAfterError() {

when(noteLoader.load())

.thenThrow(
RetrofitError.networkError("url", new IOException()))

.thenReturn(new Note(123, "aaa", "bbb"));



rule.launchActivity(null);



onView(withText(R.string.retry)).perform(click());



onView(withText(“aaa"))
.check(matches(isDisplayed()));

onView(withText(“bbb"))
.check(matches(isDisplayed()));

}

GDG DevFest – Milano – October 2015 – @fabioCollini 55
View ViewModel MockLoader
perform(click())
update
binding
Model
request
response
EspressoTest
View ViewModel MockLoaderModelEspressoTest
onView
verify
NoteLoader
NoteLoader
when().thenReturn()
onClick
binding
GDG DevFest – Milano – October 2015 – @fabioCollini 56
Android Model View ViewModel
Activity (or Fragment) is the View
All the business logic is in the ViewModel
ViewModel is managed using Dependency Injection
Model is the Activity (or Fragment) state
ViewModel is retained on configuration change
ViewModel is testable using a JVM test
GDG DevFest – Milano – October 2015 – @fabioCollini 57
Links
mockito.org
joel-costigliola.github.io/assertj
Jay Fields - Working Effectively with Unit Tests
Michael Feathers - Working Effectively with Legacy Code
medium.com/@fabioCollini/android-data-binding-f9f9d3afc761
github.com/fabioCollini/mv2m
github.com/commit-non-javisti/CoseNonJavisteAndroidApp
GDG DevFest – Milano – October 2015 – @fabioCollini 58
Thanks for your attention!
androidavanzato.it
Questions?

More Related Content

What's hot

What's hot (20)

Spring GraphQL
Spring GraphQLSpring GraphQL
Spring GraphQL
 
How to Install Python on Windows
How to Install Python on WindowsHow to Install Python on Windows
How to Install Python on Windows
 
Gradle Kotlin 컨벤션 플러그인으로 효율적으로 멀티 모듈 관리하기
Gradle Kotlin 컨벤션 플러그인으로 효율적으로 멀티 모듈 관리하기Gradle Kotlin 컨벤션 플러그인으로 효율적으로 멀티 모듈 관리하기
Gradle Kotlin 컨벤션 플러그인으로 효율적으로 멀티 모듈 관리하기
 
Kotlin
KotlinKotlin
Kotlin
 
Visual studio code
Visual studio codeVisual studio code
Visual studio code
 
Graphql Overview By Chirag Dodia
Graphql Overview By Chirag DodiaGraphql Overview By Chirag Dodia
Graphql Overview By Chirag Dodia
 
Eclipse IDE
Eclipse IDEEclipse IDE
Eclipse IDE
 
Visual studio
Visual studioVisual studio
Visual studio
 
Android MVVM
Android MVVMAndroid MVVM
Android MVVM
 
Basic concepts for python web development
Basic concepts for python web developmentBasic concepts for python web development
Basic concepts for python web development
 
Android Navigation Component
Android Navigation ComponentAndroid Navigation Component
Android Navigation Component
 
Android data binding
Android data bindingAndroid data binding
Android data binding
 
Android Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUKAndroid Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUK
 
Spring introduction
Spring introductionSpring introduction
Spring introduction
 
ASP.NET MVC Presentation
ASP.NET MVC PresentationASP.NET MVC Presentation
ASP.NET MVC Presentation
 
Introduction to Scala
Introduction to ScalaIntroduction to Scala
Introduction to Scala
 
The Essentials of AWS IoT Device Management (IOT326-R1) - AWS re:Invent 2018
The Essentials of AWS IoT Device Management (IOT326-R1) - AWS re:Invent 2018The Essentials of AWS IoT Device Management (IOT326-R1) - AWS re:Invent 2018
The Essentials of AWS IoT Device Management (IOT326-R1) - AWS re:Invent 2018
 
Serialization and performance in Java
Serialization and performance in JavaSerialization and performance in Java
Serialization and performance in Java
 
Java spring framework
Java spring frameworkJava spring framework
Java spring framework
 
Android with kotlin course
Android with kotlin courseAndroid with kotlin course
Android with kotlin course
 

Viewers also liked

DevNext - Web Programming Concepts Using Asp Net
DevNext - Web Programming Concepts Using Asp NetDevNext - Web Programming Concepts Using Asp Net
DevNext - Web Programming Concepts Using Asp Net
Adil Mughal
 

Viewers also liked (20)

Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015
 
Android Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG FirenzeAndroid Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG Firenze
 
Introduction to Retrofit and RxJava
Introduction to Retrofit and RxJavaIntroduction to Retrofit and RxJava
Introduction to Retrofit and RxJava
 
Data Binding in Action using MVVM pattern
Data Binding in Action using MVVM patternData Binding in Action using MVVM pattern
Data Binding in Action using MVVM pattern
 
MVVM with DataBinding on android
MVVM with DataBinding on androidMVVM with DataBinding on android
MVVM with DataBinding on android
 
Android Meetup Slovenija #3 - Testing with Robolectric by Ivan Kust
Android Meetup Slovenija #3 - Testing with Robolectric by Ivan KustAndroid Meetup Slovenija #3 - Testing with Robolectric by Ivan Kust
Android Meetup Slovenija #3 - Testing with Robolectric by Ivan Kust
 
Moderne App-Architektur mit Dagger2 und RxJava
Moderne App-Architektur mit Dagger2 und RxJavaModerne App-Architektur mit Dagger2 und RxJava
Moderne App-Architektur mit Dagger2 und RxJava
 
Android Widget @ whymca 2011
Android Widget @ whymca 2011Android Widget @ whymca 2011
Android Widget @ whymca 2011
 
Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014
 
Android talks #08 dagger2
Android talks #08   dagger2Android talks #08   dagger2
Android talks #08 dagger2
 
Librerie su Android: come non reinventare la ruota @ whymca 2012
Librerie su Android: come non reinventare la ruota @ whymca 2012 Librerie su Android: come non reinventare la ruota @ whymca 2012
Librerie su Android: come non reinventare la ruota @ whymca 2012
 
Model-View-ViewModel and RxJava
Model-View-ViewModel and RxJavaModel-View-ViewModel and RxJava
Model-View-ViewModel and RxJava
 
Data binding
Data bindingData binding
Data binding
 
What's New in Visual Studio 2010
What's New in Visual Studio 2010What's New in Visual Studio 2010
What's New in Visual Studio 2010
 
Windows 7 For Geeks
Windows 7 For GeeksWindows 7 For Geeks
Windows 7 For Geeks
 
Community Contribution Experience
Community Contribution ExperienceCommunity Contribution Experience
Community Contribution Experience
 
DevNext - Web Programming Concepts Using Asp Net
DevNext - Web Programming Concepts Using Asp NetDevNext - Web Programming Concepts Using Asp Net
DevNext - Web Programming Concepts Using Asp Net
 
Dominando o Data Binding no Android
Dominando o Data Binding no AndroidDominando o Data Binding no Android
Dominando o Data Binding no Android
 
Android Databinding Library
Android Databinding LibraryAndroid Databinding Library
Android Databinding Library
 
Code Sharing Between Windows Phone/Store Apps
Code Sharing Between Windows Phone/Store AppsCode Sharing Between Windows Phone/Store Apps
Code Sharing Between Windows Phone/Store Apps
 

Similar to Testable Android Apps using data binding and MVVM

Google Plus SignIn : l'Authentification Google
Google Plus SignIn : l'Authentification GoogleGoogle Plus SignIn : l'Authentification Google
Google Plus SignIn : l'Authentification Google
Mathias Seguy
 
Guice tutorial
Guice tutorialGuice tutorial
Guice tutorial
Anh Quân
 

Similar to Testable Android Apps using data binding and MVVM (20)

Встреча Google Post IO ( Владимир Иванов, Катерина Заворотченко и Сергей Комлач)
Встреча Google Post IO ( Владимир Иванов, Катерина Заворотченко и Сергей Комлач)Встреча Google Post IO ( Владимир Иванов, Катерина Заворотченко и Сергей Комлач)
Встреча Google Post IO ( Владимир Иванов, Катерина Заворотченко и Сергей Комлач)
 
Avoiding and dealing with conflicting updates in Oak
Avoiding and dealing with conflicting updates in OakAvoiding and dealing with conflicting updates in Oak
Avoiding and dealing with conflicting updates in Oak
 
Google Plus SignIn : l'Authentification Google
Google Plus SignIn : l'Authentification GoogleGoogle Plus SignIn : l'Authentification Google
Google Plus SignIn : l'Authentification Google
 
Google GIN
Google GINGoogle GIN
Google GIN
 
Guice tutorial
Guice tutorialGuice tutorial
Guice tutorial
 
Dagger for dummies
Dagger for dummiesDagger for dummies
Dagger for dummies
 
How to code to code less
How to code to code lessHow to code to code less
How to code to code less
 
Android architecture
Android architecture Android architecture
Android architecture
 
Griffon Presentation
Griffon PresentationGriffon Presentation
Griffon Presentation
 
My way to clean android v2 English DroidCon Spain
My way to clean android v2 English DroidCon SpainMy way to clean android v2 English DroidCon Spain
My way to clean android v2 English DroidCon Spain
 
Loopback: An Easy and Robust Mobile Backend - Michael Hantler & Aviv Callande...
Loopback: An Easy and Robust Mobile Backend - Michael Hantler & Aviv Callande...Loopback: An Easy and Robust Mobile Backend - Michael Hantler & Aviv Callande...
Loopback: An Easy and Robust Mobile Backend - Michael Hantler & Aviv Callande...
 
Sharper Better Faster Dagger ‡ - Droidcon SF
Sharper Better Faster Dagger ‡ - Droidcon SFSharper Better Faster Dagger ‡ - Droidcon SF
Sharper Better Faster Dagger ‡ - Droidcon SF
 
ColdBox APIs + VueJS - powering Mobile, Desktop and Web Apps with 1 VueJS cod...
ColdBox APIs + VueJS - powering Mobile, Desktop and Web Apps with 1 VueJS cod...ColdBox APIs + VueJS - powering Mobile, Desktop and Web Apps with 1 VueJS cod...
ColdBox APIs + VueJS - powering Mobile, Desktop and Web Apps with 1 VueJS cod...
 
Modern android development
Modern android developmentModern android development
Modern android development
 
Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023
Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023
Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023
 
Advanced Dagger talk from 360andev
Advanced Dagger talk from 360andevAdvanced Dagger talk from 360andev
Advanced Dagger talk from 360andev
 
GitOps: Git come unica fonte di verità per applicazioni e infrastruttura
GitOps: Git come unica fonte di verità per applicazioni e infrastrutturaGitOps: Git come unica fonte di verità per applicazioni e infrastruttura
GitOps: Git come unica fonte di verità per applicazioni e infrastruttura
 
Predictable Web Apps with Angular and Redux
Predictable Web Apps with Angular and ReduxPredictable Web Apps with Angular and Redux
Predictable Web Apps with Angular and Redux
 
Using Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture projectUsing Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture project
 
Bridging the Gap: Single-Page Apps and AEM
Bridging the Gap: Single-Page Apps and AEMBridging the Gap: Single-Page Apps and AEM
Bridging the Gap: Single-Page Apps and AEM
 

More from Fabio Collini

More from Fabio Collini (16)

Architectures in the compose world
Architectures in the compose worldArchitectures in the compose world
Architectures in the compose world
 
Using hilt in a modularized project
Using hilt in a modularized projectUsing hilt in a modularized project
Using hilt in a modularized project
 
Managing parallelism using coroutines
Managing parallelism using coroutinesManaging parallelism using coroutines
Managing parallelism using coroutines
 
Kotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community confKotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community conf
 
Kotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere StockholmKotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere Stockholm
 
Solid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalySolid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon Italy
 
SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
SOLID principles in practice: the Clean Architecture - Devfest Emila RomagnaSOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
 
SOLID principles in practice: the Clean Architecture
SOLID principles in practice: the Clean ArchitectureSOLID principles in practice: the Clean Architecture
SOLID principles in practice: the Clean Architecture
 
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf MilanFrom Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
 
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinAsync code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
 
Recap Google I/O 2018
Recap Google I/O 2018Recap Google I/O 2018
Recap Google I/O 2018
 
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italyFrom java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
 
From java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+kFrom java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+k
 
Testing Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UKTesting Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UK
 
Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2
 
Testing Android apps based on Dagger and RxJava
Testing Android apps based on Dagger and RxJavaTesting Android apps based on Dagger and RxJava
Testing Android apps based on Dagger and RxJava
 

Recently uploaded

%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
masabamasaba
 
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
masabamasaba
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
VictoriaMetrics
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
masabamasaba
 

Recently uploaded (20)

WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
 
tonesoftg
tonesoftgtonesoftg
tonesoftg
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
 
Architecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastArchitecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the past
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
WSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go Platformless
 
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
WSO2Con2024 - From Code To Cloud: Fast Track Your Cloud Native Journey with C...
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
%in Harare+277-882-255-28 abortion pills for sale in Harare
%in Harare+277-882-255-28 abortion pills for sale in Harare%in Harare+277-882-255-28 abortion pills for sale in Harare
%in Harare+277-882-255-28 abortion pills for sale in Harare
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 

Testable Android Apps using data binding and MVVM

  • 1. Testable Android Apps using data binding and MVVM Fabio Collini
  • 2. GDG DevFest – Milano – October 2015 – @fabioCollini 2 Ego slide @fabioCollini linkedin.com/in/fabiocollini Folder Organizer cosenonjaviste.it nana bianca Freapp instal.com Rain tomorrow?
  • 3. GDG DevFest – Milano – October 2015 – @fabioCollini 3 Agenda 1. ROI and Legacy code 2. Model View ViewModel 3. JVM Unit tests 4. Mockito 5. Espresso
  • 4. GDG DevFest - Milano - October 2015 - @fabioCollini 4 1ROI and legacy code
  • 5. GDG DevFest – Milano – October 2015 – @fabioCollini 5 Quick survey Do you write automated tests?
  • 6. GDG DevFest – Milano – October 2015 – @fabioCollini 6
  • 7. GDG DevFest – Milano – October 2015 – @fabioCollini 7 Return of Investment - ROI Net profit Investment
  • 8. GDG DevFest – Milano – October 2015 – @fabioCollini 8 Legacy code Edit and pray Vs Cover and modify Legacy code is code without unit tests
  • 9. GDG DevFest – Milano – October 2015 – @fabioCollini 9 Test After Development Write the feature implementation Do some manual testing Try to write automatic tests Modify the initial implementation to test it “Standard” Android code is not testable :(
  • 10. GDG DevFest – Milano – October 2015 – @fabioCollini 10 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
  • 11. GDG DevFest - Milano - October 2015 - @fabioCollini 11 2Model View ViewModel
  • 12. GDG DevFest – Milano – October 2015 – @fabioCollini 12 Testable code Data binding and MVVM
  • 13. GDG DevFest – Milano – October 2015 – @fabioCollini 13 Model View ViewModel View ViewModel Model DataBinding
  • 14. GDG DevFest – Milano – October 2015 – @fabioCollini 14 Android Model View ViewModel View ViewModel Model DataBinding Retained on configuration change Saved in Activity or Fragment state Activity or Fragment
  • 15. GDG DevFest – Milano – October 2015 – @fabioCollini 15 mv2m https://github.com/fabioCollini/mv2m
  • 16. GDG DevFest – Milano – October 2015 – @fabioCollini 16 NoteActivity NoteViewModel NoteModel note_detail.xml NoteDetailBinding DataBinding
  • 17. GDG DevFest – Milano – October 2015 – @fabioCollini 17 View ViewModel RetrofitService onClick update binding Model View ViewModel RetrofitServiceModel request response binding
  • 18. GDG DevFest – Milano – October 2015 – @fabioCollini 18 NoteModel Saved on Activity state public class NoteModel implements Parcelable {
 
 private long noteId;
 
 private ObservableBoolean error = new ObservableBoolean();
 
 private ObservableString title = new ObservableString();
 
 private ObservableString text = new ObservableString();
 
 private ObservableInt titleError = new ObservableInt();
 
 private ObservableInt textError = new ObservableInt();
 
 //...
 }
  • 19. GDG DevFest – Milano – October 2015 – @fabioCollini 19 NoteActivity public class NoteActivity extends ViewModelActivity<NoteViewModel> {
 
 @Override public NoteViewModel createViewModel() {
 return new NoteViewModel(/* .. */);
 }
 
 @Override protected void onCreate(Bundle state) {
 super.onCreate(state);
 NoteDetailBinding binding = DataBindingUtil.setContentView(this, R.layout.note_detail);
 binding.setViewModel(viewModel);
 }
 }
  • 20. GDG DevFest – Milano – October 2015 – @fabioCollini 20 note_detail.xml <?xml version="1.0" encoding="utf-8"?>
 <layout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto">
 
 <data>
 <variable
 name="viewModel"
 type="it.cosenonjaviste.core.NoteViewModel"/>
 </data>
 
 <FrameLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent">
 
 <!-- ... -->
 
 </FrameLayout>
 </layout>
  • 21. GDG DevFest – Milano – October 2015 – @fabioCollini 21 note_detail.xml <LinearLayout app:visible="@{viewModel.loading}">
 <ProgressBar />
 <TextView />
 </LinearLayout>
 
 <LinearLayout app:visible=“@{viewModel.model.error}"> 
 <TextView android:text="@string/error_loading_note"/>
 
 <Button android:text=“@string/retry"/>
 </LinearLayout>
 
 <ScrollView app:visible="@{!viewModel.loading &amp;&amp; !viewModel.model.error}">
 <!-- ... -->
 </ScrollView>
  • 22. GDG DevFest – Milano – October 2015 – @fabioCollini 22 note_detail.xml <LinearLayout>
 <android.support.design.widget.TextInputLayout
 app:error=“@{viewModel.model.titleError}"> 
 <EditText app:binding=“@{viewModel.model.title}" /> 
 </android.support.design.widget.TextInputLayout>
 <!-- ... --> <RelativeLayout> 
 <Button android:enabled="@{!viewModel.sending}"
 app:onClick="@{viewModel.save}"/>
 <ProgressBar app:visible=“@{viewModel.sending}" />
 </RelativeLayout>
 </LinearLayout>
  • 23. GDG DevFest – Milano – October 2015 – @fabioCollini 23 app:binding @BindingAdapter({"app:binding"})
 public static void bindEditText(EditText view, final ObservableString observableString) {
 if (view.getTag(R.id.binded) == null) {
 view.setTag(R.id.binded, true);
 view.addTextChangedListener(new TextWatcherAdapter() {
 @Override public void onTextChanged( CharSequence s, int st, int b, int c) {
 observableString.set(s.toString());
 }
 });
 }
 String newValue = observableString.get();
 if (!view.getText().toString().equals(newValue)) {
 view.setText(newValue);
 }
 }
  • 24. GDG DevFest – Milano – October 2015 – @fabioCollini 24 app:visible app:onClick @BindingAdapter({"app:visible"})
 public static void bindVisible(View view, boolean b) {
 view.setVisibility(b ? View.VISIBLE : View.INVISIBLE);
 }
 
 @BindingAdapter({"app:onClick"})
 public static void bindOnClick(View view, final Runnable listener) {
 view.setOnClickListener(new View.OnClickListener() {
 @Override public void onClick(View v) {
 listener.run();
 }
 });
 }
  • 25. GDG DevFest – Milano – October 2015 – @fabioCollini 25 NoteViewModel public class NoteViewModel extends ViewModel<NoteModel> {
 
 //...
 
 @Override public NoteModel createDefaultModel() {
 return new NoteModel();
 }
 
 @Override public void resume() {
 if (!getModel().isLoaded()) {
 reloadData();
 }
 }
 
 public void reloadData() {
 }
 
 //...
 }
  • 26. GDG DevFest - Milano - October 2015 - @fabioCollini 26 3JVM Unit tests
  • 27. GDG DevFest – Milano – October 2015 – @fabioCollini 27 Instrumentation tests run on a device (real or emulated) high code coverage Vs JVM tests fast low code coverage
  • 28. GDG DevFest – Milano – October 2015 – @fabioCollini JVM Test 28 NoteActivity NoteViewModel NoteModel note_detail.xml NoteDetailBinding DataBinding
  • 29. GDG DevFest – Milano – October 2015 – @fabioCollini 29 NoteViewModel.reloadData public class NoteViewModel extends ViewModel<NoteModel> {
 
 //...
 @Override public void resume() {
 if (!getModel().isLoaded()) {
 reloadData();
 }
 }
 
 public void reloadData() {
 try {
 Note note = NoteLoader.singleton().load();
 getModel().update(note);
 } catch (Exception e) {
 getModel().getError().set(true);
 }
 }
 //...
 }
  • 30. GDG DevFest – Milano – October 2015 – @fabioCollini 30 First test AssertJ @Test
 public void testLoadData() {
 NoteViewModel viewModel = new NoteViewModel();
 
 NoteModel model = viewModel.initAndResume();
 
 assertThat(model.getTitle().get()).isEqualTo("???");
 assertThat(model.getText().get()).isEqualTo("???");
 assertThat(model.getError().get()).isFalse();
 }
  • 31. GDG DevFest – Milano – October 2015 – @fabioCollini 31 NoteLoader.singleton public class NoteViewModel extends ViewModel<NoteModel> {
 
 //...
 @Override public void resume() {
 if (!getModel().isLoaded()) {
 reloadData();
 }
 }
 
 public void reloadData() {
 try {
 Note note = NoteLoader.singleton().load();
 getModel().update(note);
 } catch (Exception e) {
 getModel().getError().set(true);
 }
 }
 //...
 }
  • 32. GDG DevFest – Milano – October 2015 – @fabioCollini 32 Dependency Injection public class NoteViewModel extends ViewModel<NoteModel, NoteView> { private NoteLoader noteLoader;
 public NoteViewModel(NoteLoader noteLoader) {
 this.noteLoader = noteLoader;
 } 
 public void reloadData() {
 try {
 Note note = noteLoader.load();
 getModel().update(note);
 } catch (Exception e) {
 getModel().getError().set(true);
 }
 } //... }
  • 33. GDG DevFest – Milano – October 2015 – @fabioCollini 33 NoteLoaderStub public class NoteLoaderStub implements NoteLoader { 
 private Note note;
 
 public NoteLoaderStub(Note note) {
 this.note = note;
 }
 
 @Override public Note load() {
 return note;
 }
 }
  • 34. GDG DevFest – Milano – October 2015 – @fabioCollini 34 Test with stub @Test
 public void testLoadData() {
 NoteLoaderStub stub = new NoteLoaderStub(new Note(1, "a", "b")); 
 NoteViewModel viewModel = new NoteViewModel(stub);
 
 NoteModel model = viewModel.initAndResume();
 
 assertThat(model.getTitle().get()).isEqualTo("a");
 assertThat(model.getText().get()).isEqualTo("b");
 assertThat(model.getError().get()).isFalse();
 }
  • 35. GDG DevFest – Milano – October 2015 – @fabioCollini public void save() {
 NoteModel model = getModel();
 boolean titleValid = checkMandatory( model.getTitle(), model.getTitleError());
 boolean textValid = checkMandatory( model.getText(), model.getTextError());
 if (titleValid && textValid) {
 try {
 noteSaver.save( model.getNoteId(), model.getTitle().get(), model.getText().get()); messageManager.showMessage(R.string.note_saved);
 } catch (RetrofitError e) {
 messageManager.showMessage( R.string.error_saving_note);
 }
 }
 } 35 save method Dependency Injection Dependency Injection
  • 36. GDG DevFest – Milano – October 2015 – @fabioCollini 36 SnackbarMessageManager public class SnackbarMessageManager implements MessageManager {
 private Activity activity;
 
 @Override public void showMessage(int message) {
 if (activity != null) {
 Snackbar.make( activity.findViewById(android.R.id.content), message, Snackbar.LENGTH_LONG ).show();
 }
 }
 
 @Override public void setActivity(Activity activity) {
 this.activity = activity;
 }
 }
  • 37. GDG DevFest – Milano – October 2015 – @fabioCollini 37 MessageManagerSpy public class MessageManagerSpy implements MessageManager {
 public int message;
 
 @Override public void showMessage(int message) {
 this.message = message;
 }
 
 @Override public void setActivity(Activity activity) {
 }
 }
  • 38. GDG DevFest – Milano – October 2015 – @fabioCollini 38 NoteSaverSpy public class NoteSaverSpy implements NoteSaver {
 
 public long id;
 public String title;
 public String text;
 
 @Override public Response save( long id, String title, String text) {
 this.id = id;
 this.title = title;
 this.text = text;
 return null;
 }
 }
  • 39. GDG DevFest – Milano – October 2015 – @fabioCollini 39 Test with spy @Test
 public void testSaveData() {
 NoteLoaderStub stub = new NoteLoaderStub(new Note(1, "a", "b")); NoteSaverSpy saverSpy = new NoteSaverSpy();
 MessageManagerSpy messageSpy = new MessageManagerSpy(); NoteViewModel viewModel = new NoteViewModel( stub, saverSpy, messageSpy); 
 NoteModel model = viewModel.initAndResume();
 model.getTitle().set("newTitle");
 model.getText().set("newText");
 viewModel.save();
 
 assertThat(saverSpy.id).isEqualTo(1L);
 assertThat(saverSpy.title).isEqualTo("newTitle");
 assertThat(saverSpy.text).isEqualTo("newText"); assertThat(messageSpy.message) .isEqualTo(R.string.note_saved);
 }
  • 40. GDG DevFest - Milano - October 2015 - @fabioCollini 40 4Mockito
  • 41. GDG DevFest – Milano – October 2015 – @fabioCollini 41 Mockito @Test
 public void testLoadData() {
 NoteLoader noteLoader =
 Mockito.mock(NoteLoader.class);
 NoteSaver noteSaver = 
 Mockito.mock(NoteSaver.class); MessageManager messageManager = Mockito.mock(MessageManager.class); 
 NoteViewModel viewModel = new NoteViewModel(
 noteLoader, noteSaver, messageManager);
 
 when(noteLoader.load())
 .thenReturn(new Note(123, "title", "text"));
 NoteModel model = viewModel.initAndResume();
 
 assertThat(model.getTitle().get()).isEqualTo("title");
 assertThat(model.getText().get()).isEqualTo("text");
 }
  • 42. GDG DevFest – Milano – October 2015 – @fabioCollini MockLoader MockLoaderNoteLoader NoteLoader 42 ViewModel initAndResume update Model request response JVM Test ViewModel ModelJVM Test assert when().thenReturn()
  • 43. GDG DevFest – Milano – October 2015 – @fabioCollini 43 Mockito @Test
 public void testSaveData() {
 //... 
 NoteModel model = viewModel.initAndResume();
 
 model.getTitle().set("newTitle");
 model.getText().set("newText");
 viewModel.save();
 
 verify(noteSaver) .save(eq(123L), eq("newTitle"), eq("newText"));
 
 verify(messageManager) .showMessage(eq(R.string.note_saved));
 }

  • 44. GDG DevFest – Milano – October 2015 – @fabioCollini MockMessage Manager Message Manager MockMessage Manager Message Manager 44 ViewModel MockSaver save showMessage Model request response JVM Test ViewModel MockSaverModelJVM Test verify verify NoteSaver NoteSaver
  • 45. GDG DevFest – Milano – October 2015 – @fabioCollini 45 SetUp method public class NoteViewModelTest {
 private NoteLoader noteLoader;
 private NoteSaver noteSaver;
 private MessageManager messageManager; private NoteViewModel viewModel;
 
 @Before public void setUp() {
 noteLoader = Mockito.mock(NoteLoader.class);
 noteSaver = Mockito.mock(NoteSaver.class);
 messageManager = Mockito.mock(MessageManager.class); 
 viewModel = new NoteViewModel( noteLoader, noteSaver, messageManager); when(noteLoader.load())
 .thenReturn(new Note(123, "title", "text"));
 }
 //...
 }
  • 46. GDG DevFest – Milano – October 2015 – @fabioCollini 46 @Mock and @InjectMocks @RunWith(MockitoJUnitRunner.class)
 public class NoteViewModelTest {
 
 @Mock NoteLoader noteLoader;
 
 @Mock NoteSaver noteSaver;
 
 @Mock MessageManager messageManager;
 
 @InjectMocks NoteViewModel viewModel;
 
 @Before public void setUp() throws Exception {
 when(noteLoader.load())
 .thenReturn(new Note(123, "title", "text"));
 } 
 //...
 }
  • 47. GDG DevFest – Milano – October 2015 – @fabioCollini 47 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)
  • 48. GDG DevFest – Milano – October 2015 – @fabioCollini 48 Background executor if (titleValid && textValid) {
 sending.set(true);
 backgroundExecutor.execute(new Runnable() {
 @Override public void run() {
 try {
 noteSaver.save(getModel().getNoteId(), getModel().getTitle().get(), getModel().getText().get());
 hideSendProgressAndShowMessage( R.string.note_saved);
 } catch (RetrofitError e) {
 hideSendProgressAndShowMessage( R.string.error_saving_note);
 }
 }
 });
 }
  • 49. GDG DevFest – Milano – October 2015 – @fabioCollini 49 Ui Executor private void hideSendProgressAndShowMessage(final int msg) {
 uiExecutor.execute(new Runnable() {
 @Override public void run() {
 messageManager.showMessage(msg);
 sending.set(false);
 }
 });
 }

  • 50. GDG DevFest – Milano – October 2015 – @fabioCollini 50 Test using single thread @RunWith(MockitoJUnitRunner.class)
 public class NoteViewModelTest {
 
 @Mock NoteView view;
 
 @Mock NoteLoader noteLoader;
 
 @Mock NoteSaver noteSaver;
 
 @Spy Executor executor = new Executor() {
 @Override public void execute(Runnable command) {
 command.run();
 }
 };
 
 @InjectMocks NoteViewModel viewModel;
 
 //...
 }
  • 51. GDG DevFest - Milano - October 2015 - @fabioCollini 51 5Espresso
  • 52. GDG DevFest – Milano – October 2015 – @fabioCollini 52 NoteLoader public class NoteLoader {
 private static NoteLoader instance;
 
 public static NoteLoader singleton() {
 if (instance == null) {
 instance = new NoteLoader();
 }
 return instance;
 }
 
 private NoteLoader() {
 }
 
 @VisibleForTesting
 public static void setInstance(NoteLoader instance) {
 NoteLoader.instance = instance;
 }
 
 //...
 }
  • 53. GDG DevFest – Milano – October 2015 – @fabioCollini public class NoteActivityTest { 
 @Rule public ActivityTestRule<NoteActivity> rule = new ActivityTestRule<>(NoteActivity.class, false, false);
 
 private NoteLoader noteLoader; 
 @Before public void setUp() throws Exception {
 noteLoader = Mockito.mock(NoteLoader.class);
 NoteLoader.setInstance(noteLoader);
 } //...
 } 53 NoteActivityTest
  • 54. GDG DevFest – Milano – October 2015 – @fabioCollini 54 Reload test @Test
 public void testReloadAfterError() {
 when(noteLoader.load())
 .thenThrow( RetrofitError.networkError("url", new IOException()))
 .thenReturn(new Note(123, "aaa", "bbb"));
 
 rule.launchActivity(null);
 
 onView(withText(R.string.retry)).perform(click());
 
 onView(withText(“aaa")) .check(matches(isDisplayed()));
 onView(withText(“bbb")) .check(matches(isDisplayed()));
 }

  • 55. GDG DevFest – Milano – October 2015 – @fabioCollini 55 View ViewModel MockLoader perform(click()) update binding Model request response EspressoTest View ViewModel MockLoaderModelEspressoTest onView verify NoteLoader NoteLoader when().thenReturn() onClick binding
  • 56. GDG DevFest – Milano – October 2015 – @fabioCollini 56 Android Model View ViewModel Activity (or Fragment) is the View All the business logic is in the ViewModel ViewModel is managed using Dependency Injection Model is the Activity (or Fragment) state ViewModel is retained on configuration change ViewModel is testable using a JVM test
  • 57. GDG DevFest – Milano – October 2015 – @fabioCollini 57 Links mockito.org joel-costigliola.github.io/assertj Jay Fields - Working Effectively with Unit Tests Michael Feathers - Working Effectively with Legacy Code medium.com/@fabioCollini/android-data-binding-f9f9d3afc761 github.com/fabioCollini/mv2m github.com/commit-non-javisti/CoseNonJavisteAndroidApp
  • 58. GDG DevFest – Milano – October 2015 – @fabioCollini 58 Thanks for your attention! androidavanzato.it Questions?